zoukankan      html  css  js  c++  java
  • 图论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;
    }
  • 相关阅读:
    支付宝-单笔转账接口
    # Creating Server TCP listening socket *:6379: bind: No such file or directory
    Window 下安装 Redis,配置redis环境变量
    Ajax的跨域(一)
    web人脸识别(二)
    web人脸识别(一)
    给GridView添加列头复选框
    计算两个时间相差多少年月日的sql算法
    MUI下拉加载安卓手机无效的解决方法
    博主回来了!
  • 原文地址:https://www.cnblogs.com/chen-1/p/12567099.html
Copyright © 2011-2022 走看看