• 分层图最短路


    模版:洛谷P4568 飞行路线

    对于同一个点的各种状态,把它们分别放到各层图里的同一个点上,根据这些状态的关系进行同层或跨层的转移。

    升维写法类似图上动态规划。对路径有额外要求,在普通的最短路上给dis数组上加维表示状态就行

    比如此题就是dis[i][j]表示当走到i点,还剩j张免费劵时花费的最小值

    在单源最短路的基础上有2种情况

    第一种是不用劵转移到下一节点,花钱转移。dis[to][fr]=dis[id][fr]+edge[i].w

    第二种是用劵转移,这是不需要花钱,但余下免费劵的数量减1。dis[to][fr-1]=dis[id][fr]

    注意第二种情况当免费劵已经用完时不能转移

    这道题目只需要更新这2种情况,有些可以叠加使用的题目就得多一重循环转移了

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    using namespace std;
    
    const int INF=2147483647;
    int n,m,K,Start,End;
    struct star{//链式前向星 
        int u,v,w;
    }edge[100005];
    int last[100005],next[100005];
    void addedge(int u,int v,int w){
        m++;
        edge[m]=(star){u,v,w};
    }
    void starinit(){//前向星初始化 
        for(int i=1;i<=n;i++) last[i]=-1;
        for(int i=1;i<=m;i++){
            int flag=edge[i].u;
            next[i]=last[flag];
            last[flag]=i;
        }
    }
    struct em{//加到优先队列里的结构体 
        int id,fr,val;
        bool operator<(em uuz)const{//重载,STL里的优先队列默认是大根堆 
            return val>uuz.val;
        }
    };
    int dis[100005][15];
    priority_queue<em>heap;
    void dij(int sta){//dijkstra+优先队列 
        for(int i=1;i<=n;i++)for(int j=0;j<=K;j++) dis[i][j]=INF;
        dis[sta][K]=0;
        heap.push((em){sta,K,0});
        for(;!heap.empty();){
            em now=heap.top();
            int id=now.id;
            int fr=now.fr;
            heap.pop();
            if(now.val!=dis[id][fr]) continue;//判断是否被废弃 
            for(int i=last[id];i!=-1;i=next[i]){
                int to=edge[i].v;
                if(dis[to][fr]>dis[id][fr]+edge[i].w){//更新不用免费劵的情况 
                    dis[to][fr]=dis[id][fr]+edge[i].w;
                    heap.push((em){to,fr,dis[to][fr]});
                }
                if(fr-1>=0&&dis[to][fr-1]>dis[id][fr]){//更新使用免费劵的情况 
                    dis[to][fr-1]=dis[id][fr];
                    heap.push((em){to,fr-1,dis[to][fr-1]});
                }
            }
        }
    }
    int main(){
        int cirno;
        cin>>n>>cirno>>K;
        cin>>Start>>End;
        Start++;End++;//把编号弄成正整数方便处理 
        for(int i=1;i<=cirno;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            u++;v++;//同上 
            addedge(u,v,w);
            addedge(v,u,w);
        }
        starinit();
        dij(Start);
        int ans=INF;
        for(int i=0;i<=K;i++){//有可能没用完劵 
            ans=min(ans,dis[End][i]);
        }
        cout<<ans;
        return 0;
    }
    View Code(升维流,dij,old)

    还有另一种写法,是建了个真正的分层图,各层节点用 (层数-1)*层节点总数+编号 表示,层与层通过建边直接连接以转移状态。面对复杂的情况更易用,但空间占用会稍微多一点。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<ctime>
    #include<queue>
    using namespace std;
    const int MXN=2000000,INF=999999999;
    int n,m,K,Sta,End;
    struct Edge{
        int v,w;
    }edge[int(1.5*MXN)];
    int last[MXN],nxt[int(1.5*MXN)],en;
    void AddEdge(int u,int v,int w){
        edge[++en]=(Edge){v,w};
        nxt[en]=last[u];
        last[u]=en;
    }
    struct QElt{
        int id,val;
        bool operator<(QElt x)const{return val>x.val;}
    };
    int dis[MXN];
    void Dij(int S){
        for(int i=0;i<MXN;i++) dis[i]=INF;
        priority_queue<QElt>heap;
        heap.push((QElt){S,0});
        dis[S]=0;
        while(!heap.empty()){
            QElt x=heap.top();heap.pop();
            int u=x.id,val=x.val;
            if(dis[u]!=val) continue;
            for(int i=last[u];i!=0;i=nxt[i]){
                int v=edge[i].v,w=edge[i].w;
                if(dis[v]>dis[u]+w){
                    dis[v]=dis[u]+w;
                    heap.push((QElt){v,dis[v]});
                }
            }
        }
    }
    struct Input{
        int u,v,w;
    }ip[MXN];
    int main(){
        cin>>n>>m>>K>>Sta>>End;Sta++;End++;
        en=0;for(int i=0;i<MXN;i++) last[i]=0;
        for(int i=1;i<=m;i++){
            int u,v,w;scanf("%d%d%d",&u,&v,&w);
            u++;v++;ip[i]=(Input){u,v,w};
        }
        for(int i=0;i<=K;i++){
            for(int j=1;j<=m;j++){
                int u=ip[j].u,v=ip[j].v,w=ip[j].w;
                AddEdge(i*n+u,i*n+v,w);AddEdge(i*n+v,i*n+u,w);//不用劵,同层转移 
                if(i!=K){//如果没用完,可以用劵,免费移动并换层(已用劵数增加1) 
                    AddEdge(i*n+u,(i+1)*n+v,0),
                    AddEdge(i*n+v,(i+1)*n+u,0);
                }
            }
        }
        Dij(Sta);
        int ans=INF;
        for(int i=0;i<=K;i++) ans=min(ans,dis[i*n+End]);
        cout<<ans;
        return 0;
    }
    View Code(建图流,dij)

    以及某个被卡成70pt的SPFA...

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<ctime>
    #include<queue>
    using namespace std;
    const int MXN=2000000,INF=999999999;
    int n,m,K,Sta,End;
    struct Edge{
        int v,w;
    }edge[int(1.5*MXN)];
    int last[MXN],nxt[int(1.5*MXN)],en;
    void AddEdge(int u,int v,int w){
        edge[++en]=(Edge){v,w};
        nxt[en]=last[u];
        last[u]=en;
    }
    int que[MXN*5],isq[MXN],head,tail;
    int dis[MXN];
    void SPFA(int S){
        for(int i=0;i<MXN;i++) isq[i]=0,dis[i]=INF;
        head=tail=0;
        que[tail++]=S;
        isq[S]=1;dis[S]=0;
        while(head<tail){
            int u=que[head++];
            for(int i=last[u];i!=0;i=nxt[i]){
                int v=edge[i].v,w=edge[i].w;
                if(dis[v]>dis[u]+w){
                    dis[v]=dis[u]+w;
                    if(!isq[v]) que[tail++]=v,isq[v]=1;
                }
            }
            isq[u]=0;
        }
    }
    struct Input{
        int u,v,w;
    }ip[MXN];
    int main(){
        cin>>n>>m>>K>>Sta>>End;Sta++;End++;
        en=0;for(int i=0;i<MXN;i++) last[i]=0;
        for(int i=1;i<=m;i++){
            int u,v,w;scanf("%d%d%d",&u,&v,&w);
            u++;v++;ip[i]=(Input){u,v,w};
        }
        for(int i=0;i<=K;i++){
            for(int j=1;j<=m;j++){
                int u=ip[j].u,v=ip[j].v,w=ip[j].w;
                AddEdge(i*n+u,i*n+v,w);AddEdge(i*n+v,i*n+u,w);
                if(i!=K){
                    AddEdge(i*n+u,(i+1)*n+v,0),
                    AddEdge(i*n+v,(i+1)*n+u,0);
                }
            }
        }
        SPFA(Sta);
        int ans=INF;
        for(int i=0;i<=K;i++) ans=min(ans,dis[i*n+End]);
        cout<<ans;
        return 0;
    }
    View Code(建图流,spfa,70pt)

    例题:P4009 汽车加油行驶问题

    稍微有点复杂了。容易想到以油量剩余的多少来分层,请先自己想一想如何转移状态。详情见代码吧

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<ctime>
    #include<queue>
    using namespace std;
    const int PTN=200000,EDN=1000000,INF=999999999;
    int n,K,A,B,C,sqrn;
    struct Edge{
        int v,w;
    }edge[EDN];
    int last[PTN],nxt[EDN],en;
    void AddEdge(int u,int v,int w){
        edge[++en]=(Edge){v,w};
        nxt[en]=last[u];
        last[u]=en;
    }
    int que[EDN*5],isq[PTN],head,tail;
    int dis[PTN];
    void SPFA(int S){
        for(int i=0;i<PTN;i++) isq[i]=0,dis[i]=INF;
        head=tail=0;
        que[tail++]=S;
        isq[S]=1;dis[S]=0;
        while(head<tail){
            int u=que[head++];
            for(int i=last[u];i!=0;i=nxt[i]){
                int v=edge[i].v,w=edge[i].w;
                if(dis[v]>dis[u]+w){
                    dis[v]=dis[u]+w;
                    if(!isq[v]) que[tail++]=v,isq[v]=1;
                }
            }
            isq[u]=0;
        }
    }
    int map[500][500];
    int main(){
        cin>>n>>K>>A>>B>>C;sqrn=n*n;
        en=0;for(int i=0;i<PTN;i++) last[i]=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                scanf("%d",&map[i][j]);
        int fang[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
        for(int q=0;q<=K;q++){//开始建图 
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    int cid=(i-1)*n+j;//二维编号转单编号 
                    for(int k=0;k<4;k++){
                        int nx=i+fang[k][0],ny=j+fang[k][1];
                        if(q==0||nx==0||nx==n+1||ny==0||ny==n+1) continue;
                        //油量为0时不能前进 
                        int nid=(nx-1)*n+ny;//二维编号转单编号
                        int w=0;if(k>=2) w=B;//倒退情况 
                        if(map[i][j]==1&&q!=K) continue;//如果油没满,强制加油后前进 
                        AddEdge(q*sqrn+cid,(q-1)*sqrn+nid,w);//位置移动,油量减1 
                    }
                    if(map[i][j]==1) AddEdge(q*sqrn+cid,K*sqrn+cid,A);//有加油站,只付油费 
                    else AddEdge(q*sqrn+cid,K*sqrn+cid,A+C);//没加油站,建了再付
                }
            }
        }SPFA(K*sqrn+1);
        int ans=INF;
        for(int i=0;i<=K;i++)
            ans=min(ans,dis[i*sqrn+sqrn]);
        cout<<ans;
        return 0;
    }
    View Code(建图流,dij)
  • 相关阅读:
    《张艺谋这个人》较真
    《智能》是真智能
    《解密小米之互联网下的商业奇迹》
    《三毛。。。。》烂漫
    《盛典―― 诺奖之行》
    常用iOS、Mac框架和库及常用中文开发博客
    《人脸识别与人体动作识别技术及应用》
    《程序员第二步从程序员到项目经理》
    《信息安全导论》
    [leetCode]141.环形链表
  • 原文地址:https://www.cnblogs.com/sun123zxy/p/layershortestpath.html
Copyright © 2020-2023  润新知