三解<关路灯luogu1220>

题目见:洛谷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点.用本题的测试数据进行的记忆化搜索图如下:
Markdown

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值