• 升降梯上——玄学dp


    升降梯上

    题目描述

    开启了升降梯的动力之后,探险队员们进入了升降梯运行的那条竖直的隧道,映入眼帘的是一条直通塔顶的轨道、一辆停在轨道底部的电梯、和电梯内一杆控制电梯升降的巨大手柄。

    (Nescafe) 之塔一共有N层,升降梯在每层都有一个停靠点。手柄有M个控制槽,第i个控制槽旁边标着一个数 (C_i),满足 (C_1<C_2<C_3<……<C_M)。如果 (C_i>0),表示手柄扳动到该槽时,电梯将上升 (C_i) 层;如果 (C_i<0),表示手柄扳动到该槽时,电梯将下降 (-C_i) 层;并且一定存在一个 (C_i=0),手柄最初就位于此槽中。注意升降梯只能在 (1-N) 层间移动,因此扳动到使升降梯移动到 (1) 层以下、(N) 层以上的控制槽是不允许的。

    电梯每移动一层,需要花费 (2) 秒钟时间,而手柄从一个控制槽扳到相邻的槽,需要花费 (1) 秒钟时间。探险队员现在在 (1) 层,并且想尽快到达 (N) 层,他们想知道从 (1) 层到 (N) 层至少需要多长时间?

    输入格式

    第一行两个正整数 (N)(M)

    第二行M个整数 (C_1)(C_2……C_M)

    输出格式

    输出一个整数表示答案,即至少需要多长时间。若不可能到达输出 (-1)

    样例

    样例输入

    6 3
    -1 0 2
    

    样例输出

    19
    

    数据范围与提示

    手柄从第二个槽扳到第三个槽((0) 扳到 (2)),用时 (1) 秒,电梯上升到 (3) 层,用时 (4) 秒。

    手柄在第三个槽不动,电梯再上升到 (5) 层,用时 (4) 秒。

    手柄扳动到第一个槽((2) 扳到 (-1)),用时 (2) 秒,电梯下降到 (4) 层,用时 (2) 秒。

    手柄扳动到第三个槽((-1) 扳倒 (2)),用时 (2) 秒,电梯上升到 (6) 层,用时 (4) 秒。

    总用时为 ((1+4)+4+(2+2)+(2+4)=19) 秒。

    对于 (30\%) 的数据,满足 (1leq Nleq 10,2leq Mleq 5)

    对于 (100\%) 的数据,满足 (1leq Nleq 1000,2leq Mleq 20,-N<C_1<C_2<……<C_M<N)

    思路

    这个题做法很多,本博主只讲其中 (dp) 的玄学做法,若想看最短路,(Dfs)等做法,出门右转

    首先我们可以定义一个 (dp[i][j]),表示当手柄在第 (i) 的位置时,走到了第 (j) 层。

    我们上一个状态不能确定到底是在哪个手柄,所以要挨个枚举一遍。

    时间加上移动手柄的时间和升降梯移动的时间,去最小值即可。

    动态转移方程:

    if(i==k){//这个判断加不加都可
          dp[i][j]=min(dp[k][j-c[i]]+abs(c[i])*2,dp[i][j]);
    }else{
          dp[i][j]=min(dp[k][j-c[i]]+abs(i-k)+abs(c[i])*2,dp[i][j]);
    }
    

    思路很好想,但是实现时会发现仅进行一遍,有些点并没有更新。

    所以我们进行多次 (dp),发现到第 (4) 次时,所有的值都进行了更新,而且不再改变。

    若我们在一次 (dp) 中,正反都进行一遍,只需 (3) 次即可。

    若只正向进行,所需的次数就很玄学,保险起见直接 (20) 次。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn=1000+50,INF=0x3f3f3f3f;
    int n,m;
    int x0;
    int c[maxn];
    int dp[50][maxn];
    int ans=INF;
    bool cmp(int a,int b){
          return a>b;
    }
    
    int main(){
          memset(dp,0x3f,sizeof(dp));//初始为最大
          scanf("%d%d",&n,&m);
          for(int i=1;i<=m;i++){
                scanf("%d",&c[i]);
    	    if(c[i]==0){
                      x0=i;//找到手柄的起始点
                }
          }
          for(int i=1;i<=m;i++){
                dp[i][1]=abs(i-x0);//初始化
          }
          dp[x0][1]=0;//初始化
          for(int s=1;s<=20;s++){//保险20次
                for(int i=1;i<=m;i++){//当前状态
                      for(int j=2;j<=n;j++){//当前层数
                            for(int k=1;k<=m;k++){//上一个状态
                                  if(j-c[i]<=0||j-c[i]>n)continue;//若上一个状态越界,直接跳过
                                  if(i==k){//可省略判断
                                        dp[i][j]=min(dp[k][j-c[i]]+abs(c[i])*2,dp[i][j]);
                                  }else{
                                        dp[i][j]=min(dp[k][j-c[i]]+abs(i-k)+abs(c[i])*2,dp[i][j]);
                                  }
                            }
                      }
                }
          }
          for(int i=1;i<=m;i++){
    	      ans=min(ans,dp[i][n]);
          }
          if(ans==INF){
    	      printf("-1
    ");
          }else{
    	      printf("%d
    ",ans);
          }
          return 0;
    }
    
  • 相关阅读:
    firefox显示 您的连接不安全 解决办法
    【TweenMax】to():添加动画
    【TweenMax】实例TimelineMax
    【js】document.all用法
    【js】阻止默认事件
    【封装】【JS】getClassName方法、get class+id封装
    【HTML】html结构,html5结构
    【实例】原生 js 实现全屏滚动效果
    【音乐】播放器
    GO : 斐波纳契数列
  • 原文地址:https://www.cnblogs.com/Rubyonly233/p/13251790.html
Copyright © 2020-2023  润新知