「杂烩」升降梯上

「杂烩」升降梯上

题面:

题目描述

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

(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

数据范围与提示

手柄从第二个槽扳到第三个槽((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% 的数据,满足$ 1≤N≤10,2<=M<=5$。

对于 100% 的数据,满足(1≤N≤1000,2<=M<=20,-N<C1<C2<……<CM<N)

解法1:暴搜

开题一看:1≤N≤1000,2<=M<=20,貌似搜索也挂不了多少,直接肛

代码:



/*#!/bin/sh
dir=$GEDIT_CURRENT_DOCUMENT_DIR
name=$GEDIT_CURRENT_DOCUMENT_NAME
pre=${name%.*}
g++ -O2 $dir/$name -o $pre -g -Wall -std=c++11
if test $? -eq 0; then
    gnome-terminal -x bash -c "time $dir/$pre;echo;read;"
fi*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=2e3+5,INF=0x3f3f3f3f;
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n,m,c[maxn],ans=INF,vis[maxn][maxn],f[maxn];
void DFS(int now1,int now2,int sum){//now1是当前层,now2是当前手柄,sum是达到此状态的时间
	if(f[now1]>=sum)return;//!!!!!五连警告,考场败在这了,奇怪的终止搜索方法:用新数组存sum,
	f[now1]=sum;//如果发现当前的层原来搜过,那么时间一定比原来要长,否则就是又搜了一遍,需要终止递归
	if(now1==n){ans=min(ans,sum);return;}
	for(int i=m;i>=1;i--){
		if(now1+c[i]<1||now1+c[i]>n||c[i]==0)continue;//不合法状态
		DFS(now1+c[i],i,sum+abs(i-now2)+2*abs(c[i]));//新时间
	}
}
int main(){
	freopen("a.in","r",stdin);
	n=read(),m=read();
	int now=1;
	for(int i=1;i<=m;i++){
		c[i]=read();
		if(c[i]==0)now=i;
	}
	memset(f,0x3f,sizeof(f));
	DFS(1,now,0);
	if(ans==INF)cout<<-1;
	else cout<<ans;
	return 0;
}

解法2:动归

一看又上又下又有状态当然要想到动归,这题状态转移方程很好想:

(f[i][j]=min(f[i][j],f[i-c[j]][k]+abs(j-k)+2*abs(a[j])))

其中i表示当前层,j表示当前手柄,k表示上一手柄(貌似跟暴搜很像)

预处理:(f[1][now]=0) (我在初始位置当然是零时刻)

细节问题(来自某考试过程中五分钟推出关系式见样例不过转身走向搜索的):我们不管怎么去转移最后一定是有剩余的状态还未参与最后转移的,因为当前状态既可能从上面几层转移过来,也可能从下面几层转移过来的,所以不管正着倒着枚举都不是最优秀的办法。

低端的解决办法:多跑几遍

高端的解决办法:对每一次大循环的f数组判断一下,如果不再变化了说明都达到了最终转移状态

低端版代码



/*#!/bin/sh
dir=$GEDIT_CURRENT_DOCUMENT_DIR
name=$GEDIT_CURRENT_DOCUMENT_NAME
pre=${name%.*}
g++ -O2 $dir/$name -o $pre -g -Wall -std=c++11
if test $? -eq 0; then
    gnome-terminal -x bash -c "time $dir/$pre;echo;read;"
fi*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e3+5,INF=0x3f3f3f3f;
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n,m,c[maxn],f[maxn][50],ans=INF,g[maxn][50];
int main(){
//	freopen("a.in","r",stdin);
	n=read(),m=read();
	int now=1;
	for(int i=1;i<=m;i++){
		c[i]=read();
		if(c[i]==0)now=i;
	}
	memset(f,0x3f,sizeof(f));
	f[1][now]=0;
	for(int p=1;p<=m;p++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				if(i-c[j]>n||i-c[j]<1)continue;
				for(int k=1;k<=m;k++)
					f[i][j]=min(f[i][j],f[i-c[j]][k]+abs(j-k)+2*abs(c[j]));	
			}
			
		}
	}
	for(int i=1;i<=m;i++)ans=min(ans,f[n][i]);
	if(ans==INF)cout<<-1;
	else cout<<ans;
	return 0;
}

高端版



/*#!/bin/sh
dir=$GEDIT_CURRENT_DOCUMENT_DIR
name=$GEDIT_CURRENT_DOCUMENT_NAME
pre=${name%.*}
g++ -O2 $dir/$name -o $pre -g -Wall -std=c++11
if test $? -eq 0; then
    gnome-terminal -x bash -c "time $dir/$pre;echo;read;"
fi*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e3+5,INF=0x3f3f3f3f;
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n,m,c[maxn],f[maxn][50],ans=INF,g[maxn][50];
bool panduan(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(g[i][j]!=f[i][j])return 1;
		}
	}
	return 0;
}
void turnback(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			g[i][j]=f[i][j];
		}
	}
}
int main(){
//	freopen("a.in","r",stdin);
	n=read(),m=read();
	int now=1;
	for(int i=1;i<=m;i++){
		c[i]=read();
		if(c[i]==0)now=i;
	}
	memset(f,0x3f,sizeof(f));
	f[1][now]=0;
	for(int i=1;i<=m;i++){if(now!=i)f[1][i]=abs(i-now);}
	while(panduan()){
		turnback();
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				if(j==now)continue;
				if(i-c[j]>n||i-c[j]<1)continue;
				for(int k=1;k<=m;k++){
					if(k==now)continue;
					f[i][j]=min(f[i][j],f[i-c[j]][k]+abs(j-k)+2*abs(c[j]));	
					//cout<<i<<"=i j="<<j<<" k= "<<k<<" i-k="<<i-c[k]<<" f[i][j] ="<<f[i][j]<<endl;
				}
				if(i==n)ans=min(ans,f[n][j]);
			}
			
		}
	}
	if(ans==INF)cout<<-1;
	else cout<<ans;
	return 0;
}

解法3:最短路

万万想不到能用DP还要用最短路(某道馅饼题跑了拓扑,直接从100pts->40pts)

1.二维压一维
2.奇妙的建图

代码



/*#!/bin/sh
dir=$GEDIT_CURRENT_DOCUMENT_DIR
name=$GEDIT_CURRENT_DOCUMENT_NAME
pre=${name%.*}
g++ -O2 $dir/$name -o $pre -g -Wall -std=c++11
if test $? -eq 0; then
    gnome-terminal -x bash -c "time $dir/$pre;echo;read;"
fi*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int maxn=4e7+5,INF=0x3f3f3f3f;
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n,m,c[maxn],ans=INF,head[maxn],tot,dis[maxn],vis[maxn];
int turnit(int x,int y){
	return (x-1)*m+y;
}
struct Edge{
	int next,to,dis;
}e[maxn];
void Add(int x,int y,int z){
	e[++tot].to=y;
	e[tot].next=head[x];
	e[tot].dis=z;
	head[x]=tot;
}
queue<int> q;
void spfa(int S){
	for(int i=1;i<=n*m;i++)vis[i]=0,dis[i]=INF;
	dis[S]=0;vis[S]=1;q.push(S);
	while(!q.empty()){
		int u=q.front();q.pop();vis[u]=0;
		for(int x=head[u];x;x=e[x].next){
			int v=e[x].to;
			if(dis[v]>dis[u]+e[x].dis){
				dis[v]=dis[u]+e[x].dis;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	
	
}
int main(){
	
	n=read(),m=read();
	int now=1;
	for(int i=1;i<=m;i++){
		c[i]=read();
		if(c[i]==0)now=i;
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	for(int k=1;k<=m;k++)
		Add(turnit(i,j),turnit(i+c[k],k),abs(j-k)+2*abs(c[k]));
	spfa(turnit(1,now));
	for(int i=1;i<=m;i++){
		ans=min(dis[turnit(n,i)],ans);
		//cout<<dis[turnit(n,i)]<<endl;
	}
	if(ans==INF)cout<<-1;
	else cout<<ans;
	return 0;
}

原文地址:https://www.cnblogs.com/614685877--aakennes/p/13251544.html