暑假集训Day13 升降梯上
题目大意
开启了升降梯的动力之后,探险队员们进入了升降梯运行的那条竖直的隧道,映入眼帘的是一条直通塔顶的轨道、一辆停在轨道底部的电梯、和电梯内一杆控制电梯升降的巨大手柄。
Nescafe之塔一共有N层,升降梯在每层都有一个停靠点。手柄有M个控制槽,第i个控制槽旁边标着一个数Ci,满足C1<C2<C3<……<CM。如果Ci>0,表示手柄扳动到该槽时,电梯将上升Ci层;如果Ci<0,表示手柄扳动到该槽时,电梯将下降-Ci层;并且一定存在一个Ci=0,手柄最初就位于此槽中。注意升降梯只能在1~N层间移动,因此扳动到使升降梯移动到1层以下、N层以上的控制槽是不允许的。
电梯每移动一层,需要花费2秒钟时间,而手柄从一个控制槽扳到相邻的槽,需要花费1秒钟时间。探险队员现在在1层,并且想尽快到达N层,他们想知道从1层到N层至少需要多长时间?
输入格式
第一行两个正整数N、M。
第二行M个整数C1、C2……CM。
输出格式
输出一个整数表示答案,即至少需要多长时间。若不可能到达输出-1。
样例
样例输入
6 3
-1 0 2
样例输出
19
算法分析
- 莫名用奇怪的dp 就给A掉了 正解是最短路(奇怪的最短路增加了)
- 来讲讲我奇怪的dp吧
- 定义一个数组f[i][j]表示当前在第i层第j个按钮出需要最少的时间
- 然后枚举状态的时候用三层循环 第一层枚举i 第二层枚举j 第三层枚举k作为上次是在哪个按钮
- 然后转移方程就很简单了 直接搞起就好了
- 但是这样的话为啥要叫奇怪的dp呢?因为我们如果直接这样转移的话 会发现不对(输出会是我们初始化的0x3f3f3f3f)所以我们要做一些
猥琐很妙的事情:多跑几遍! - 没错宁木得看错就是多跑几遍 关于为啥多跑几遍就对了呢? 其实我们很容易就可以想到 : 因为我们既可以上楼也可以下楼 所以如果我们无论正着跑 倒着跑 显然都是不对的 (dalao可以尝试加上一堆限制条件和特盘与初始化),那该咋办呢,那我们就既正着跑 也反着跑 然后再正着跑 这样的话将前两次的作为第三次跑的初始值就木得问题啦(复杂度的话也不用太担心 毕竟这样除了一些
丧心病狂优美异常的卡常题以外 还是可以过去的 毕竟多跑两遍也就是常数的变化) - 下面看代码吧
代码展示
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+10;
int n,m,dp[maxn][maxn],dp2[maxn][maxn],c[maxn];
int ans=0x3f3f3f3f;
int x0;
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;++i){
scanf("%d",&c[i]);//输出各个按钮的上下楼
if(c[i] == 0)x0 = i;//找到起点
}
memset(dp,0x3f,sizeof(dp));//初始化
dp[1][x0]=0;//初始化,在一层x0的位置显然是0
for(int i = 1;i <= m;++i)dp[1][i] = abs(i-x0);//还是初始化
for(int i = 2;i <= n;++i){//从第二层跑到第n层
for(int j = 1;j <= m;++j){//枚举按钮
for(int k = 1;k <= m;k++){//上个按钮
if(i - c[j] < 1 || i - c[j] > n)continue;//不满足条件的情况
dp[i][j] = min(dp[i][j],dp[i - c[j]][k] + abs(j-k) + abs(c[j]) * 2);//开始dp
}
}
}
for(int i = n;i >= 2;--i){//倒着跑一遍 (这不就操作起来了嘛)
for(int j = 1;j <= m;++j){//同第一个
for(int k = 1;k <= m;k++){
if(i - c[j] < 1 || i - c[j] > n)continue;
dp[i][j] = min(dp[i][j],dp[i - c[j]][k] + abs(j-k) + abs(c[j]) * 2);
}
}
}
for(int i = 2;i <= n;++i){//操作就完了呗
for(int j = 1;j <= m;++j){
for(int k = 1;k <= m;k++){
if(i - c[j] < 1 || i - c[j] > n)continue;
dp[i][j] = min(dp[i][j],dp[i - c[j]][k] + abs(j-k) + abs(c[j]) * 2);
}
}
}
for(int i = 1;i <= m;++i)ans=min(ans,dp[n][i]);//寻找区间最值
if(ans == 0x3f3f3f3f)printf("-1\n");//到不了第n层
else cout<<ans<<endl;
return 0;
}
如初见 与初见