最短路模型总结
基础知识
单源最短路
Dijkstra
void dijkstra() { memset(vis,0,sizeof vis); memset(dis,0x3f,sizeof dis); dis[1]=0; for(int i=1;i<=n-1;i++) { int p=0; for(int j=1;j<=n;j++) if(!vis[j]&&(p==0||dis[j]<dis[p]))p=j; vis[p]=1; for(int j=1;j<=n;j++) dis[j]=min(dis[j],dis[p]+w[p][j]); } }
Dijkstra_Heap
void dijkstra() { memset(vis,0,sizeof vis); memset(dis,0x3f,sizeof dis); priority_queue<pair<int,int> > q; dis[0]=0; q.push(make_pair(0,0)); while(!q.empty()) { int k=q.top().second; q.pop(); if(vis[k])continue; vis[k]=1; for(int i=h[k];~i;i=edge[i].next) { int to=edge[i].to,w=edge[i].w; if(dis[to]>dis[k]+w) { dis[to]=dis[k]+w; q.push(make_pair(-dis[to],to)); } } } }
SPFA
void SPFA() { memset(vis,0,sizeof vis); memset(dis,0x3f,sizeof dis); queue<int> q; dis[1]=0,vis[1]=1; q.push(1); while(!q.empty()) { int p=q.front(); q.pop(); vis[p]=0; for(int i=h[p];~i;i=edge[i].next) { int to=edge[i].to,w=edge[i].w; if(dis[to]>dis[p]+w) { dis[to]=dis[p]+w; if(!vis[to])q.push(to),vis[to]=1; } } } }
小结
算法 | 限制 | 时间复杂度 |
Dijkstra | 无法处理负权边,遍历过程具有拓扑序 | O(n2) |
Dijkstra_Heap | 无法处理负权边,遍历过程具有拓扑序 | O(mlogn) |
Bellman_Ford | 可处理负权边,时间成本太高,遍历过程不具拓扑序 | O(nm) |
SPFA | 可处理负权边,遍历过程不具拓扑序 | O(km),特殊情况下可能退化成O(nm) |
全源最短路
Johnson
时间复杂度:O(nmlogn)
Floyd
时间复杂度:O(n3)
void Floyd() { for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); }
经典模型
虚拟源点
算法概述
建立一个虚拟源点,从虚拟源点向各个起始点连一条权值为0的边。
则原问题等价于从虚拟源点到终点的最短路径长度。
如此便成功将一个多源最短路问题转化为了一个单源最短路问题。
参考代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int N=1e3+10,M=2e4+10; struct Edge{ int to,next,w; }edge[M+N<<1];int idx; int h[N]; int dis[N],vis[N]; int n,m,s,t; void add_edge(int u,int v,int w){edge[++idx]={v,h[u],w};h[u]=idx;} void dijkstra() { memset(vis,0,sizeof vis); memset(dis,0x3f,sizeof dis); priority_queue<pair<int,int> > q; dis[0]=0; q.push(make_pair(0,0)); while(!q.empty()) { int k=q.top().second; q.pop(); if(vis[k])continue; vis[k]=1; for(int i=h[k];~i;i=edge[i].next) { int to=edge[i].to,w=edge[i].w; if(dis[to]>dis[k]+w) { dis[to]=dis[k]+w; q.push(make_pair(-dis[to],to)); } } } } int main() { while(~scanf("%d%d%d",&n,&m,&t)) { memset(h,-1,sizeof h); while(m--) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add_edge(u,v,w); } scanf("%d",&s); for(int i=1;i<=s;i++) { int x;scanf("%d",&x); add_edge(0,x,0); } dijkstra(); if(dis[t]==0x3f3f3f3f)printf("-1\n"); else printf("%d\n",dis[t]); } return 0; }
分层图(拆点)
算法概述
分层图实际上是借助动态规划的思想考虑最短路问题,而后再用求最短路的算法来解决的一种思想。
考虑在每层的图上各点间连原有的边,在相邻层之间连权值为0的边。
而后原问题就转化为从第一层图的起点走到最后一层图的终点的最短路问题。
参考代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int N=1e4+10,M=5e4+10,MAXK=11; struct Edge{ int to,next,w; }edge[M*MAXK*4]; int idx; int h[N*MAXK]; int dis[N*MAXK],vis[N*MAXK]; int n,m,K,s,t; void add_edge(int u,int v,int w){edge[++idx]={v,h[u],w};h[u]=idx;} void dijkstra(int s) { memset(vis,0,sizeof vis); memset(dis,0x3f,sizeof dis); priority_queue<pair<int,int> > q; dis[s]=0; q.push(make_pair(0,s)); while(!q.empty()) { int k=q.top().second; q.pop(); if(vis[k])continue; vis[k]=1; for(int i=h[k];~i;i=edge[i].next) { int to=edge[i].to,w=edge[i].w; if(dis[to]>dis[k]+w) { dis[to]=dis[k]+w; q.push(make_pair(-dis[to],to)); } } } } int main() { memset(h,-1,sizeof h); scanf("%d%d%d",&n,&m,&K); scanf("%d%d",&s,&t); while(m--) { int u,v,w;scanf("%d%d%d",&u,&v,&w); for(int i=0;i<=K;i++) { add_edge(u+i*n,v+i*n,w); add_edge(v+i*n,u+i*n,w); } for(int i=0;i<K;i++) { add_edge(u+i*n,v+(i+1)*n,0); add_edge(v+i*n,u+(i+1)*n,0); } } for(int i=0;i<K;i++)add_edge(t+i*n,t+(i+1)*n,0); dijkstra(s); printf("%d\n",dis[t+K*n]); }
01最短路
算法概述
对于边权只有0和1的图,求最短路可以用双端队列BFS。
在每次松弛操作时,若边权为0则放入队首,若边权为1则放到队尾。
参考代码
void BFS() { memset(dis,0x3f,sizeof dis); memset(vis,0,sizeof vis); deque<int> q; dis[1]=0; q.push_front(1); while(!q.empty()) { int p=q.front(); q.pop_front(); if(vis[p])continue; vis[p]=1; for(int i=h[p];~i;i=edge[i].ne) { int to=edge[i].to,w=edge[i].w>mid; if(dis[to]>dis[p]+w) { dis[to]=dis[p]+w; if(w)q.push_back(to); else q.push_front(to); } } } }
最短路计数
算法概述
在Dijkstra原算法上稍作修改。
每次松驰操作更新最短路时,同时更新计数数组。
若更新不成功,转而判断当前路径是否与当前最短路长度相等,若是则累加计数。
说明:
该题可用Dijkstra算法求解的一个必要条件是:Dijkstra算法求解的过程,对图的遍历顺序是该图的一个拓扑序。
而SPFA算法不满足拓扑序,故不便于求解该类问题。
参考代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int N=1e6+10,M=2e6+10,mod=1e5+3; struct Edge{ int to,next,w; }edge[M<<1]; int idx; int h[N]; int dis[N],vis[N],cnt[N]; int n,m; void add(int u,int v,int w){edge[++idx]={v,h[u],w};h[u]=idx;} void dijkstra(int s) { memset(vis,0,sizeof vis); memset(dis,0x3f,sizeof dis); priority_queue<pair<int,int> > q; dis[s]=0,cnt[s]=1; q.push(make_pair(0,s)); while(!q.empty()) { int k=q.top().second; q.pop(); if(vis[k])continue; vis[k]=1; for(int i=h[k];~i;i=edge[i].next) { int to=edge[i].to,w=edge[i].w; if(dis[to]>dis[k]+w) { dis[to]=dis[k]+w; cnt[to]=cnt[k]; q.push(make_pair(-dis[to],to)); } else if(dis[to]==dis[k]+w) { (cnt[to]+=cnt[k])%=mod; } } } } int main() { memset(h,-1,sizeof h); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v,1); add(v,u,1); } dijkstra(1); for(int i=1;i<=n;i++)printf("%d\n",cnt[i]); return 0; }
次短路
算法概述
该题综合了最短路、最短路计数、次短路、次短路计数,值得一做。
对于求解次短路。
仿照拆点的思想,将一个点拆成两个点,dis[i][0]表示最短路,dis[i][1]表示次短路,然后进行求解。
在原dijkstra算法上稍作修改:
若可更新最短路,则将最短路沦为次短路,然后更新最短路,同时将最短路与次短路入队;
若可更新次短路,则将次短路更新,然后将次短路入队。
参考代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int N=1e3+10,M=1e4+10; struct Edge{ int to,next,w; }edge[M];int idx; int h[N]; void add_edge(int u,int v,int w){edge[++idx]={v,h[u],w};h[u]=idx;} struct Point{ int p,tp,d; bool operator >(const Point &P)const{ return d>P.d; } }; int dis[N][2],vis[N][2],cnt[N][2]; int n,m,s,t,T; void init() { memset(h,-1,sizeof h); idx=0; } int dijkstra() { memset(cnt,0,sizeof cnt); memset(vis,0,sizeof vis); memset(dis,0x3f,sizeof dis); priority_queue<Point,vector<Point>,greater<Point> > q; dis[s][0]=0,cnt[s][0]=1; q.push({s,0,0}); while(!q.empty()) { Point P=q.top(); q.pop(); int p=P.p,tp=P.tp; if(vis[p][tp])continue; vis[p][tp]=1; for(int i=h[p];~i;i=edge[i].next) { int to=edge[i].to,w=edge[i].w; if(dis[to][0]>dis[p][tp]+w) { dis[to][1]=dis[to][0];cnt[to][1]=cnt[to][0]; q.push({to,1,dis[to][1]}); dis[to][0]=dis[p][tp]+w;cnt[to][0]=cnt[p][tp]; q.push({to,0,dis[to][0]}); } else if(dis[to][0]==dis[p][tp]+w) cnt[to][0]+=cnt[p][tp]; else if(dis[to][1]>dis[p][tp]+w) { dis[to][1]=dis[p][tp]+w; cnt[to][1]=cnt[p][tp]; q.push({to,1,dis[to][1]}); } else if(dis[to][1]==dis[p][tp]+w) cnt[to][1]+=cnt[p][tp]; } } int res=cnt[t][0]; if(dis[t][1]==dis[t][0]+1)res+=cnt[t][1]; return res; } int main() { scanf("%d",&T); while(T--) { init(); scanf("%d%d",&n,&m); while(m--) { int u,v,w;scanf("%d%d%d",&u,&v,&w); add_edge(u,v,w); } scanf("%d%d",&s,&t); printf("%d\n",dijkstra()); } return 0; }