模版:洛谷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种情况,有些可以叠加使用的题目就得多一重循环转移了
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
还有另一种写法,是建了个真正的分层图,各层节点用 (层数-1)*层节点总数+编号 表示,层与层通过建边直接连接以转移状态。面对复杂的情况更易用,但空间占用会稍微多一点。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
以及某个被卡成70pt的SPFA...
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
稍微有点复杂了。容易想到以油量剩余的多少来分层,请先自己想一想如何转移状态。详情见代码吧
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }