【ACM回顾】 最短路练习
- 最短路目前学过的几种算法
- 单源最短路
- Dijkstra
- Bellman-Ford
- SPFA
- 多源最短路
- Floyd
- 单源最短路
Dijkstra
每次取dist[p]最小的p进行拓展,要求没有负权边
注:此处【扩展】意为“取该节点的所有相邻边进行松弛”
Bellman-Ford(不常用,常用SPFA代替)
对所有边进行|V| - 1轮松弛
SPFA
对Bellman-Ford进行优化
发现只有在上一轮成功松弛的dist[u]才会在当前轮造成影响
因此开出一单向队列,只有成功松弛的dist[u]才加入待扩展队列
*可用于求解负权环,若有节点入队n次以上,则有负权环
Floyd
动态规划思想
f[k][u][v] = min {f[k-1][u][v], f[k-1][u][k] + f[k-1][k][v]}
要求没有负权环
话不多说,贴题走起
A-Til The Cow Come Home (poj2387)
裸的最短路模板题,此处采用STL优先队列优化的Dijkstra
1 #include<cstdio> 2 #include<queue> 3 #include<vector> 4 #include<cstring> 5 #include<utility> 6 #include<algorithm> 7 using namespace std; 8 9 const int N = 2000 + 10; 10 const int INF = (1<<31) - 1; 11 12 int n,t; 13 struct qnode{ 14 qnode(){} 15 qnode(int _p, int _cost) 16 { 17 p = _p; cost = _cost; 18 } 19 bool operator< (const qnode &b)const 20 { 21 return cost > b.cost; 22 } 23 24 int p,cost; 25 }; 26 priority_queue <qnode> q; 27 int cost[N], maps[N][N]; 28 bool close[N]; 29 30 void Dijkstra() 31 { 32 memset(close, 0, sizeof(close)); 33 for(int i=1;i<=n;i++) cost[i] = INF; 34 while(!q.empty()) q.pop(); 35 q.push(qnode(n, 0)); 36 cost[n] = 0; 37 //初始化 38 while(!q.empty()) 39 { 40 qnode cur = q.top(); 41 int cur_p = cur.p; 42 q.pop(); 43 if (close[cur_p]) continue; 44 if (cur_p == 1) return; 45 46 close[cur_p] = 1; 47 for(int i=1;i<=n;i++) 48 if(maps[cur_p][i] != INF) 49 { 50 if (maps[cur_p][i] + cost[cur_p] < cost[i] && !close[i]) 51 { 52 cost[i] = maps[cur_p][i] + cost[cur_p]; 53 q.push(qnode(i, cost[i])); 54 } 55 } 56 } 57 } 58 59 int main() 60 { 61 #ifndef ONLINE_JUDGE 62 freopen("poj2387.in","r",stdin); 63 #endif 64 while(scanf("%d %d",&t,&n)!=EOF) 65 { 66 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i==j) maps[i][j] = 0; else maps[i][j] = INF; 67 for(int i=1;i<=t;i++) 68 { 69 int from,to,v; 70 scanf("%d %d %d",&from,&to,&v); 71 if(maps[from][to] > v) 72 maps[from][to] = maps[to][from] = v; 73 } 74 Dijkstra(); 75 printf("%d\n",cost[1]); 76 } 77 return 0; 78 }
B-Frogger (poj2253)
题意为假设点1到点2每条路径对应一个最大边的边权v,则求出v最小的路径
两种方法,最小生成树和最短路变形
#方法一 最小生成树
显然v最小的路径必须为最小生成树上的路径,那么只要做一个“不完全”的Kruskal求最小生成路径即可
1 #include<cstdio> 2 #include<cmath> 3 #include<algorithm> 4 #include<vector> 5 #include<iostream> 6 #include<utility> 7 #include<iomanip> 8 using namespace std; 9 10 const int N = 200 + 10; 11 const double eps = 1e-7; 12 const int INF = 0xfffffff; 13 14 struct edge{ 15 int u,v; 16 double w; 17 edge(){} 18 edge(int _u, int _v, double _w) 19 { 20 u=_u; v=_v; w=_w; 21 } 22 bool operator < (const edge &b)const 23 { 24 return w < b.w; 25 } 26 }D[N*N]; int cnt = 0; 27 int n; 28 double ans; 29 int x[N],y[N]; 30 int f[N]; 31 32 int getf(int p) 33 { 34 return f[p]==p?p:f[p]=getf(f[p]); 35 } 36 37 int main() 38 { 39 int cases = 0; 40 while(1) 41 { 42 ++cases; 43 cnt = 0; 44 45 scanf("%d",&n); 46 if (!n) break; 47 48 49 for(int i=1;i<=n;i++) scanf("%d %d",&x[i],&y[i]); 50 for(int i=1;i<=n;i++) 51 for(int j=i+1;j<=n;j++) 52 D[++cnt] = edge(i, j, (double)sqrt( (double)(x[i]-x[j]) * (x[i]-x 53 54 [j]) + (y[i]-y[j]) * (y[i]-y[j])) ); 55 sort(D+1, D+cnt+1); 56 for(int i=1;i<=n;i++) f[i] = i; 57 for(int i=1;i<=cnt;i++) 58 { 59 int u=D[i].u, v=D[i].v; 60 double w = D[i].w; 61 if(getf(u) != getf(v)) f[getf(u)]=getf(v); 62 if(getf(1) == getf(2)) 63 { 64 ans = w; 65 break; 66 } 67 } 68 cout<<"Scenario #"<<cases<<endl; 69 cout<<fixed<<setprecision(3)<<"Frog Distance = "<<ans<<endl; 70 cout<<endl; 71 } 72 return 0;
#方法二 最短路变形
将dist[p]的意义变形为从起点到p之间路径上的最大边权,松弛操作变形为dist[p] = min (dist[p], max(dist[cur], edge[cur][p]) )
从这题可以体会到最短路算法的应用并不局限于求最长或最短的路径,事实上可以应用于求路径的许多性质,比如该路径上所有边权的乘积的极值,等等
唯一需要考虑的就是松弛操作的可行性
1 #include<cstdio> 2 #include<cmath> 3 #include<algorithm> 4 #include<vector> 5 #include<cstring> 6 #include<iostream> 7 #include<utility> 8 #include<iomanip> 9 #include<queue> 10 using namespace std; 11 12 const int N = 200 + 10; 13 const double INF = 0x3f3f3f3f * 1.0; 14 15 double dist[N]; 16 double maps[N][N]; 17 int x[N],y[N]; 18 int n; 19 bool close[N]; 20 typedef pair<double, int> pdi; 21 priority_queue <pdi, vector<pdi>, greater<pdi> > q; 22 23 void Dijkstra() 24 { 25 while(!q.empty()) q.pop(); 26 memset(close, 0, sizeof(close)); 27 q.push(make_pair(0, 1)); 28 for(int i=2;i<=n;i++) dist[i] = INF; 29 dist[1] = 0; 30 int cur_p; 31 32 while(!q.empty()) 33 { 34 pdi cur = q.top(); 35 cur_p = cur.second; 36 q.pop(); 37 if (close[cur_p]) continue; 38 if (cur_p == 2) break; 39 40 close[cur_p] = 1; 41 for(int i=1;i<=n;i++) 42 { 43 if(close[i]) continue; 44 if(i == cur_p) continue; 45 double maxx = max(dist[cur_p], maps[cur_p][i]); 46 if(maxx < dist[i]) 47 { 48 dist[i] = maxx; 49 q.push(make_pair(dist[i], i)); 50 } 51 } 52 53 } 54 } 55 56 int main() 57 { 58 #ifndef ONLINE_JUDGE 59 freopen("poj2253.in","r",stdin); 60 #endif 61 int cases = 0; 62 int cnt; 63 while(1) 64 { 65 ++cases; 66 cnt = 0; 67 68 scanf("%d",&n); 69 if (!n) break; 70 71 72 for(int i=1;i<=n;i++) scanf("%d %d",&x[i],&y[i]); 73 memset(maps,0,sizeof(maps)); 74 for(int i=1;i<=n;i++) 75 for(int j=i+1;j<=n;j++) 76 maps[i][j] = maps[j][i] =(double)sqrt( (double)(x[i]-x[j]) * (x[i]-x[j]) + (y[i]-y[j]) * (y[i]-y[j])); 77 Dijkstra(); 78 cout<<"Scenario #"<<cases<<endl; 79 cout<<fixed<<setprecision(3)<<"Frog Distance = "<<dist[2]<<endl; 80 cout<<endl; 81 } 82 return 0; 83 }
C-Heavy Transportation (poj1797)
和上一题极其类似,只是把“最小的最大权”换成了“最大的最小权”,一个道理
D-Silver Cow Pary (poj3268)
正反向各自建图,求两次单源最短路,相加求答案
1 #include<cstdio> 2 #include<vector> 3 #include<utility> 4 #include<queue> 5 #include<cstring> 6 using namespace std; 7 8 const int N = 1000 + 10; 9 const int INF = 0x3f3f3f3f; 10 11 typedef pair<int, int> pii; 12 vector <pii> G1[N]; 13 vector <pii> G2[N]; 14 queue <int> q; 15 int dist1[N]; 16 int dist2[N]; 17 int n,m,x; 18 bool inq[N]; 19 20 void spfa(vector <pii> G[N], int x, int *dist) 21 { 22 memset(inq,0,sizeof(inq)); 23 for(int i=1;i<=n;i++) dist[i]=INF; dist[x] = 0; 24 while(!q.empty()) q.pop(); 25 q.push(x); inq[x]=1; 26 27 while(!q.empty()) 28 { 29 int cur = q.front(); q.pop(); inq[cur] = 0; 30 int size = G[cur].size(); 31 for(int i=0;i<size;i++) 32 { 33 int p = G[cur][i].second; 34 int val = G[cur][i].first; 35 if (dist[p] > dist[cur] + val) 36 { 37 dist[p] = dist[cur] + val; 38 if(!inq[p]) 39 { 40 q.push(p); 41 inq[p]=1; 42 } 43 } 44 } 45 } 46 47 } 48 49 int main() 50 { 51 freopen("poj3268.in","r",stdin); 52 53 while(scanf("%d %d %d",&n,&m,&x)!=EOF) 54 { 55 for(int i=1;i<=m;i++) 56 { 57 int u,v,w; 58 scanf("%d %d %d",&u,&v,&w); 59 G1[u].push_back(make_pair(w,v)); 60 G2[v].push_back(make_pair(w,u)); 61 } 62 spfa(G1, x, dist1); 63 spfa(G2, x, dist2); 64 int ans = 0; 65 for(int i=1;i<=n;i++) ans = max(ans, dist1[i]+dist2[i]); 66 printf("%d\n",ans); 67 } 68 69 return 0; 70 }
E-Currency Exchange (poj1860)
又一个最短路变形,dist[p]表达为从起点到p能兑换到的最大金额,相应松弛操作为dist[p] = max (dist[p], (dist[cur] - C[cur][i]) * R[cur][i] )
在该题中利用SPFA判环即可(此处不是负权环,但是原理类似,判断入队次数是否大于n即可)
结合上一题不难发现这类变形的共同点
i)都需要求出极值(最大或最小)
ii)松弛操作都可以广义视为 当前路径 和 cur节点 性质的 合并
(比如该题中cur的性质对应dist[cur],当前路径 的性质对应C[cur][i],R[cur][i])
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<queue> 5 #include<vector> 6 #include<utility> 7 using namespace std; 8 9 const int N = 100 + 10; 10 11 struct gnode{ 12 gnode(){} 13 gnode(double _r, double _c, int _p) 14 { 15 r=_r; c=_c; p=_p; 16 } 17 int p; 18 double r,c; 19 }; 20 21 int n,m,s; 22 double sv; 23 bool inq[N]; 24 int cnt[N]; 25 double dist[N]; 26 queue <int> q; 27 vector <gnode> G[N]; 28 29 bool SPFA(int s,double sv) 30 { 31 while(!q.empty()) q.pop(); 32 memset(inq,0,sizeof(inq)); 33 memset(dist,0,sizeof(dist)); 34 memset(cnt,0,sizeof(cnt)); 35 36 dist[s]=sv; 37 q.push(s); 38 inq[s] = 1; 39 cnt[s]++; 40 while(!q.empty()) 41 { 42 int cur = q.front(); q.pop(); inq[cur] = 0; 43 int size = G[cur].size(); 44 for(int i=0;i<size;i++) 45 { 46 int p = G[cur][i].p; 47 double r = G[cur][i].r; 48 double c = G[cur][i].c; 49 if(dist[p] < (dist[cur] - c)*r) 50 { 51 dist[p] = (dist[cur] - c)*r; 52 if(!inq[p]) 53 { 54 q.push(p); 55 inq[p] = 1; 56 cnt[p] ++; 57 if(cnt[p] > n) return true; 58 } 59 } 60 } 61 } 62 return false; 63 64 } 65 66 int main() 67 { 68 #ifndef ONLINE_JUDGE 69 freopen("poj1860.in","r",stdin); 70 #endif 71 72 scanf("%d %d %d %lf",&n,&m,&s,&sv); 73 74 for(int i=1;i<=m;i++) 75 { 76 int u,v; 77 double r1,c1,r2,c2; 78 scanf("%d %d",&u,&v); 79 scanf("%lf %lf %lf %lf",&r1,&c1,&r2,&c2); 80 G[u].push_back(gnode(r1,c1,v)); 81 G[v].push_back(gnode(r2,c2,u)); 82 } 83 if(SPFA(s,sv)) 84 printf("YES\n"); 85 else 86 printf("NO\n"); 87 88 return 0; 89 }
F-Wormholes (poj 3259)
裸的负权环判定
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<queue> 5 #include<vector> 6 #include<utility> 7 using namespace std; 8 9 const int N = 500 + 10; 10 const int INF = 0x3f3f3f3f; 11 12 int n,m,w; 13 int dist[N]; 14 bool inq[N]; 15 int cnt[N]; 16 typedef pair<int, int> pii; 17 vector <pii> G[N]; 18 queue <int> q; 19 20 21 bool SPFA() 22 { 23 while(!q.empty()) q.pop(); 24 memset(inq,0,sizeof(inq)); 25 memset(cnt,0,sizeof(cnt)); 26 for(int i=1;i<=n;i++) dist[i] = INF; 27 dist[1]=0; 28 q.push(1); 29 inq[1] = 1; 30 cnt[1]++; 31 while(!q.empty()) 32 { 33 int cur = q.front(); q.pop(); inq[cur] = 0; 34 int size = G[cur].size(); 35 for(int i=0;i<size;i++) 36 { 37 int p = G[cur][i].second; 38 int v = G[cur][i].first; 39 40 if(dist[p] > dist[cur] + v) 41 { 42 dist[p] = dist[cur] + v; 43 if(!inq[p]) 44 { 45 q.push(p); 46 inq[p] = 1; 47 cnt[p] ++; 48 if(cnt[p] > n) return true; 49 } 50 } 51 } 52 } 53 return false; 54 55 } 56 57 int main() 58 { 59 #ifndef ONLINE_JUDGE 60 freopen("poj3259.in","r",stdin); 61 #endif 62 int F; 63 scanf("%d",&F); 64 65 while(F--) 66 { 67 scanf("%d %d %d",&n,&m,&w); 68 69 for(int i=1;i<=n;i++) G[i].clear(); 70 71 for(int i=1;i<=m;i++) 72 { 73 int u,v,l; 74 scanf("%d %d %d",&u,&v,&l); 75 G[u].push_back(make_pair(l,v)); 76 G[v].push_back(make_pair(l,u)); 77 } 78 for(int i=1;i<=w;i++) 79 { 80 int u,v,l; 81 scanf("%d %d %d",&u,&v,&l); 82 G[u].push_back(make_pair(-l,v)); 83 } 84 if(SPFA()) 85 printf("YES\n"); 86 else 87 printf("NO\n"); 88 } 89 return 0; 90 }
G-MPI MaelStrom (poj1502)
题意表述有点不清,但是仔细分析以后发现只用求max{dist[p] }即可,即各最短路中的最长者
实现简单,跳过
H-Cow Contest (poj3660)
对于每头牛,只要将“比X强”的传递关系传下去就好了,此处用Floyd (但是个人觉得对于每头牛做一次dfs也没毛病啊,只要传递下去就好了嘛)
这里有个关系闭包的概念,可以专门看一看