图论3-分层图最短路
这是我写的第三篇图论题解,这次要介绍的是分层图最短路。要求:熟练的打出最短路spfa/dijkstra并理解其含义。
还是照例用一道题来引入:6B02 升降梯上
题意简述:有一个电梯原先在1层,有一个控制杆可以控制电梯升降,一共有m个档(满足C1<=Ci<=Ci+1<=Cm),
将控制杆挂到相邻的档需要1秒,控制杆原本在Ci=0的那个档,在每个档都可以按一个按钮使电梯上升Ci层(当然也可
以不按并继续挂到下一档),(如果Ci<0就是下降|Ci|层),电梯到相邻的层需要2秒,求从1层到n层最少要多少秒。
思路解析:这道题有两个量,分别是挂的档以及电梯,我们要同时维护这两个量的最小,所以普通的最短路算法就没法
解决这个问题。那我们要怎么做呢?如果在dp里出现要维护两个最小时我们该怎么办?当然是把这两个量都记录到转态
里啦。也就是dp就会变成二维的,所以举一反三,在图论里我们也可以这么做。我们将这个图分成m“层”,然后再在
这m层图中跑最短路。但是由于我不想在队列里维护结构体,所以我用一个(x-1)*n+y代表第x层的y号节点,其中x层y号
节点也就是(x-1)*n+y,表示电梯停到了y层并且控制杆停到了x档的最小时间。所以最后答案就是min(dis[i*n]){1<=i<=m}
建边是这样的:
1.如果这个点对应的电梯层数+Ci>0且<=n就建一条从这个节点到这个节点+Ci,权值为abs(Ci)*2的单向边
2.对于每个控制杆档数不为m的点都向档数加一(节点编号加n)建一条权值为1的单向边,反之同理。
建边代码:
for(int i=1;i<=m;i++)//枚举层数
{ for(int j=1;j<=n;j++)//枚举节点编号 { if(a[i]==0) s=(i-1)*n+1;//找到起始点s if(j+a[i]>0&&j+a[i]<=n) add((i-1)*n+j,(i-1)*n+j+a[i],abs(a[i])*2);//建边条件1 x->x+a[i]权值为abs(a[i])*2 if(i!=1) add((i-1)*n+j,(i-2)*n+j,1); if(i!=m) add((i-1)*n+j,i*n+j,1);//建边条件2,需要正反建边,x->x+n和x->x-n权值为1 } }
注意:
1.起始点s不能默认是1
2.这也是我容易犯的错误,在spfa中,新的节点用y=to[i],之后不管什么操作千万别把i和y写反了啊
3.如果用一维分层图的话数组要开够
4.别忘记输出-1
#include<bits/stdc++.h> using namespace std; const int NR=1e3+10; const int MR=25; const int INF=0x3f3f3f3f; int n,m,s; int a[MR]; int to[NR*MR<<3],nxt[NR*MR<<3],val[NR*MR<<3]; int head[NR*MR];//邻接表数组开够 int tot=1; void add(int x,int y,int z) { to[tot]=y; val[tot]=z; nxt[tot]=head[x]; head[x]=tot++; } bool vis[NR*MR]; int dis[NR*MR];//数组开够 void spfa(int s)//spfa基本操作 { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); queue<int> q;q.push(s);dis[s]=0;vis[s]=1; while(!q.empty()) { int x=q.front();q.pop();vis[x]=0; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(dis[y]>dis[x]+val[i]) { dis[y]=dis[x]+val[i]; if(!vis[y])//注意一定是y不是i,这个错我调了很久才调出来 { q.push(y); vis[y]=1; } } } } } int read() { int x=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*f; } int main() { // freopen("1.in","r",stdin); // freopen("1.out","w",stdout); n=read(),m=read(); for(int i=1;i<=m;i++) a[i]=read();//我这里面a[i]就是题目中的Ci for(int i=1;i<=m;i++)//枚举层数 { for(int j=1;j<=n;j++)//枚举节点编号 { if(a[i]==0) s=(i-1)*n+1;//找到起始点s if(j+a[i]>0&&j+a[i]<=n) add((i-1)*n+j,(i-1)*n+j+a[i],abs(a[i])*2);//建边条件1 x->x+a[i]权值为abs(a[i])*2 if(i!=1) add((i-1)*n+j,(i-2)*n+j,1); if(i!=m) add((i-1)*n+j,i*n+j,1);//建边条件2,需要正反建边,x->x+n和x->x-n权值为1 } } spfa(s);//以求出来的起点s做最短路 int ans=INF; for(int i=1;i<=m;i++) { ans=min(ans,dis[i*n]);//统计答案要记得取min } if(ans==INF) puts("-1"); else printf("%d\n",ans);//输出答案 return 0; }