暑假集训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层
	else cout<<ans<<endl;
	return 0;
}
如初见 与初见
原文地址:https://www.cnblogs.com/HISKrrr/p/13251524.html