题目见:洛谷https://www.luogu.org/problem/show?id=1220
三解:1.暴力深搜 2.记忆化搜索 3.动态动划
一、暴力搜索
此题数据规模是0 < n <= 50,关灯的时候所经过的未关的点一定会顺手关掉,关灯的时候要么向左边走关灯,要么向右边走关灯,关的一定是向左走或向右走遇到的第一个未关的路灯.数据规模不大,暴力搜索+最优化剪枝貌似问题不会太大,因此果断搜,来看看我的暴力搜索版本:
#include<iostream>
#include<climits>
using namespace std;
int n,c;
int le= INT_MAX,rt= INT_MIN,start,startw;
int d[10001],minans = INT_MAX;
int find(int st,int vec){
while(st >= le && st <= rt){
st += vec;
if(d[st]!=0)
return st;
}
if(vec == -1) return le-1;
else return rt+1;
}
//dfs 4个参数分别为:(已关路灯数量,当前走到的位置,累计未关掉灯所消耗的功率,还未关掉的路灯总功率)
void dfs(int tot,int k,int s,int cnt){
if(s > minans ) return ; //最优化剪枝
if(tot == n){
if(s < minans) //维护最优值
{
minans = s;
}
return;
}
//左关
int weil = find(k,-1); //向左查第一个未关的路灯位置
if(weil >= le){
int x = d[weil];
d[weil] = 0;
dfs(tot+1,weil,s + (k-weil)*cnt,cnt - x);
d[weil] = x;
}
//右关
int weir = find(k,+1); //向右查第一个未关的路灯位置
if(weir <= rt){
int x = d[weir];
d[weir] = 0;
dfs(tot+1,weir,s + (weir - k)*cnt,cnt - x);
d[weir] = x;
}
}
int main(){
int total;
cin >> n >> c;
for(int i =1 ; i <= n; i++){
int x,w;
cin >> x >> w;
if(x < le) le = x; //le记录数据最左端
if(x > rt) rt = x; //rt记录数据最右端
d[x] = w;
if(i == c){
d[x] = 0;
start = x;
startw = w;
}
total += w; //所有路灯的总功率
}
dfs(1,start,0,total-startw);
cout << minans << endl;
return 0;
}
二、记忆化搜索
dp[i][j]表示已经关了i点到j点之间的路灯,再用0或1表示现在到底在哪一个点,0代表在i点,1表示在j点.用本题的测试数据进行的记忆化搜索图如下:
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
int dp[51][51][3];
int d[51],s[51];
int n,c;
int dfs(int l,int r,int vec)
{
int wei = l;
if(vec == 1)
wei = r ;
if(dp[l][r][vec] != 0x7f7f7f7f) return dp[l][r][vec] ; //记忆化
if(l == 1 && r == n) return dp[l][r][vec] = 0;
if(l - 1 >= 1 ) //向左走
{
int dis = abs(d[wei] - d[l-1]); //向左走需要走的路程
dp[l][r][vec] = min(dp[l][r][vec] , dfs(l-1,r,0)+dis * (s[n] - (s[r]-s[l-1])));
}
if(r+1 <= n) //向右走
{
int dis = abs(d[wei] - d[r+1]);//向右走需要走的路程
dp[l][r][vec] = min(dp[l][r][vec],dfs(l,r+1,1) + dis * (s[n] - (s[r]-s[l-1])));
}
return dp[l][r][vec];
}
int main()
{
cin >> n >> c;
for(int i = 1; i<= n; i++)
{
int x,w;
cin >> x >> w;
d[i] = x;
s[i] = s[i-1] + w;
}
memset(dp,0x7f,sizeof(dp));
dfs(c,c,1);
cout << min(dp[c][c][0],dp[c][c][1]) << endl;
return 0;
}
三、动态规划
想下关灯者每走s距离,未关掉的电灯就要消耗s*w。那么我们把s[n]-(s[j]-s[i-1])记为当从i到j的电灯都熄灭后剩下的灯的总功率,其中s数组为前缀和。然后进行区间动态规划。建立一个数组dp[51][51][2]。dp[i][j][0]表示从i到j这一段区间,这个人站在i点上(区间左端点);dp[i][j][1]表示走到了右边,这个人站在j点上。那么在dp时,把前一个状态 + 走到新状态的路程×剩下路灯的总功率,就是走到新状态电灯所消耗的能量。
如走到区间[i,j],可由四种情况产生:
A.到达[i,j]区间的左端点上:
1. 站在由[i+1,j]区间的左端点i+1处,走向i.所走的路程为d[i+1]-d[i],所消耗的功率:
dp[i+1][j][0] + (d[i+1] - d[i])*(s[n] - (s[j] - s[i]));
2. 站在由[i+1,j]区间的右端点j处,走向i.所走的路程为d[j]-d[i],所消耗的功率:
dp[i+1][j][1] + (d[j] - d[i])*(s[n] - (s[j] - s[i]));
B.到达[i,j]区间的右端点上:
3.站在由[i,j-1]区间的左端点i处,走向j.所走的路程为d[j]-d[i],所消耗的功率:
dp[i][j-1][0] + (s[n] -(s[j-1]-s[i-1]))* (d[j] -d[i]);
4. 站在由[i,j-1]区间的右端点j-1处,走向j.所走的路程为d[j]-d[j-1],所消耗的功率:
dp[i][j-1][1] + (s[n] - (s[j-1] - s[i-1])) * (d[j] - d[j-1]);
完整代码如下:
#include<iostream>
#include<cstring>
using namespace std;
int dp[51][51][3];
int s[51],d[51];
int main()
{
int n,c;
cin >> n >> c;
for(int i = 1; i<= n; i++)
{
int x,w;
cin >> x >> w;
d[i] = x;
s[i] = s[i-1] + w;
}
memset(dp,0x7f/2,sizeof(dp));
dp[c][c][0] = dp[c][c][1] = 0;
for(int i = c; i >=1 ; i--)
for(int j = i + 1; j<= n; j++)
{
dp[i][j][0] = min(dp[i][j][0],min(dp[i+1][j][0] + (s[n] -(s[j]-s[i]))* (d[i+1] -d[i]),dp[i+1][j][1] + (s[n] - (s[j] - s[i])) * (d[j] - d[i])));
dp[i][j][1] = min(dp[i][j][1],min(dp[i][j-1][0] + (s[n] -(s[j-1]-s[i-1]))* (d[j] -d[i]),dp[i][j-1][1] + (s[n] - (s[j-1] - s[i-1])) * (d[j] - d[j-1])));
}
cout << min(dp[1][n][0],dp[1][n][1]) << endl;
return 0;
}