分层图最短路

模版:洛谷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)

 

posted @ 2019-01-19 22:58  sun123zxy  阅读(416)  评论(0编辑  收藏  举报