图论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
",ans);//输出答案 
    return 0;
}
原文地址:https://www.cnblogs.com/chen-1/p/12567099.html