文章目录
最短路
01 UVA247 Calling Circles
-
思路:因为数据比较小,可 O ( n m ) O(nm) O(nm) 遍历查找两点对可到达的有向路径,或 O ( n 3 ) O(n^3) O(n3) 用 Floyd 找到所有有向路径。注意字符串可用 m a p map map 存下是否存在,可用 v e c t o r vector vector 对应编号。最后所有连边在同一集合的一同输出。
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x3f3f3f3f; const int N = 1e2 + 5; const db eps = 1e-10; int n, m, cas = 0; int cnt, x, y; int f[N][N], vis[N]; map<string, int> mp; vector<string> name; void init(){ mp.clear(), name.clear(); cnt = -1; memset(f, 0x3f, sizeof(f)); memset(vis, 0, sizeof(vis)); rep(i, 0, n - 1) f[i][i] = 0; } void add(string s, int &x){ if(mp.count(s)) x = mp[s]; else{ x = ++cnt; mp.insert({s, x}); name.push_back(s); } } int main(){ while(cin >> n >> m){ if(!n && !m) break; init(); //字符串转为对应编号 rep(i, 1, m){ string u, v; cin >> u >> v; add(u, x), add(v, y); f[x][y] = 1; //建图 } //floyd rep(k, 0, n - 1) rep(x, 0, n - 1) rep(y, 0, n - 1) f[x][y] = min(f[x][y], f[x][k] + f[k][y]); //输出 printf("Calling circles for data set %d:\n", ++cas); rep(u, 0, n - 1){ if(vis[u]) continue; cout << name[u]; vis[u] = 1; rep(v, 0, n - 1){ if(vis[v]) continue; if(f[u][v] < INF && f[v][u] < INF){ cout << ", " << name[v]; vis[v] = 1; } } cout << endl; } } }
02 UVa10048 Audiophobia
-
题目:无向带权图中,求两点之间道路上的边中最大权值的最小值
-
思路:一个典型的 Floyd 问题,由于求的是最大边权的最小值,只需要将转移方程改成
f[x][y] = min(f[x][y], max(f[x][k], f[k][y]))
即可#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x3f3f3f3f; const int N = 1e2 + 5; const db eps = 1e-10; int cas = 0, n, m, q; int f[N][N]; int main(){ while(cin >> n >> m >> q){ if(!n) break; memset(f, 0x3f, sizeof(f)); rep(i, 1, n) f[i][i] = 0; rep(i, 1, m){ int u, v, val; cin >> u >> v >> val; //可能有重边!!! f[u][v] = min(f[u][v], val); f[v][u] = min(f[v][u], val); } rep(k, 1, n) rep(x, 1, n) rep(y, 1, n){ if(f[x][k] != INF && f[k][y] != INF) f[x][y] = min(f[x][y], max(f[x][k], f[k][y])); } if(cas) printf("\n"); printf("Case #%d\n", ++cas); rep(i, 1, q){ int u, v; cin >> u >> v; if(f[u][v] == INF) cout << "no path" << endl; else cout << f[u][v] << endl; } } }
03 UVa1001 Say Cheese
-
思路:每个两点之间的距离可理解成直线距离减去两点半径即可,当然也不能为负。这样便是从出发点可经过 n n n 个节点最终到达结束点的最短路问题。注意最后输出时间要
* 10
和四舍五入。#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double const int N = 1e2 + 5; int cas = 0, n; db f[N][N]; //注意设成double!!! struct AC{ db x, y, z, r; }a[N]; db dis(AC a, AC b){ return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z)); } int main(){ while(cin >> n){ if(n == -1) break; rep(i, 1, n) cin >> a[i].x >> a[i].y >> a[i].z >> a[i].r; //设起点为 a[0],终点为 a[n + 1] cin >> a[0].x >> a[0].y >> a[0].z >> a[n + 1].x >> a[n + 1].y >> a[n + 1].z; a[0].r = a[n + 1].r = 0.0; rep(i, 0, n + 1) rep(j, 0, n + 1){ if(i == j) f[i][j] = 0.0; else f[i][j] = max(0.0, dis(a[i], a[j]) - a[i].r - a[j].r); } rep(k, 0, n + 1) rep(x, 0, n + 1) rep(y, 0, n + 1){ //floyd f[x][y] = min(f[x][y], f[x][k] + f[k][y]); } printf("Cheese %d: Travel time = %.0lf sec\n", ++cas, round(f[0][n + 1] * 10)); } }
04 POJ1125 Stockbroker Grapevine
-
思路:重点看清不成立输出
disjoint
的情况,即存在节点没有连通道路时(既没有输出边,也没有输入边)。此外 f l o y d floyd floyd 搜出每个点到其他点的最短路后,找到到最远点时间最短的即可。#include<iostream> #include<cmath> #include<cstring> #include<set> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x3f3f3f3f; const int N = 1e2 + 5; const db eps = 1e-10; int n, f[N][N], ans[N], ok; set<int> st; void init(){ memset(f, 0x3f, sizeof(f)); memset(ans, 0, sizeof(ans)); st.clear(); rep(i, 1, n) f[i][i] = 0; } int main(){ while(cin >> n){ if(!n) break; init(); rep(u, 1, n){ int t; cin >> t; if(t) st.insert(t); rep(i, 1, t){ int v, val; cin >> v >> val; st.insert(v); f[u][v] = val; } } if(st.size() < n){ puts("disjoint"); continue; } rep(k, 1, n) rep(x, 1, n) rep(y, 1, n){ //floyd if(f[x][k] != INF && f[k][y] != INF) f[x][y] = min(f[x][y], f[x][k] + f[k][y]); } rep(x, 1, n) rep(y, 1, n) ans[x] = max(ans[x], f[x][y]); int minn = INF, node = 0; rep(i, 1, n){ if(minn > ans[i]){ minn = ans[i]; node = i; } } cout << node << " " << minn << endl; } }
05 USACO09Oct Heat Wave G
-
思路:普通求最短路,直接 d i j k s t r a dijkstra dijkstra 即可。注意是无向图
//dijkstra堆优化 #include<bits/stdc++.h> #define rep(i, a, b) for(int i = a; i <= b; i++) #define per(i, a, b) for(int i = a; i >= b; i--) const int INF = 0x7fffffff; #define ll long long #define PII pair<int, int> const int N = 3e4 + 5; //应该记为边数 using namespace std; int n, m, s, t; int vis[N], dist[N]; vector<PII> vec[N]; void dijkstra(){ priority_queue<PII, vector<PII>, greater<PII> > heap; dist[s] = 0, heap.push({0, s}); while(heap.size()){ PII now = heap.top(); heap.pop(); int nod = now.second; if(vis[nod]) continue; vis[nod] = 1; for(auto j : vec[nod]){ int To = j.first, distmp = j.second; if(dist[To] > now.first + distmp){ dist[To] = now.first + distmp; heap.push({dist[To], To}); } } } return; } int main(){ cin >> n >> m >> s >> t; memset(vis, 0, sizeof(vis)); rep(i, 1, m){ int u, v, w; cin >> u >> v >> w; //注意是无向图 vec[u].push_back({v, w}); vec[v].push_back({u, w}); } memset(dist, 0x3f, sizeof(dist)); dijkstra(); cout << dist[t] << endl; }
06 POJ1847 Tram
-
思路:普通最短路,只不过边权为 0 0 0 或 1 1 1,而且数据太小,可以直接同 f l o y d floyd floyd
#include<iostream> #include<cmath> #include<cstring> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define PII pair<int, int> const int INF = 0x3f3f3f3f; const int N = 1e2 + 5; int n, s, t, f[N][N]; int main(){ cin >> n >> s >> t; memset(f, 0x3f, sizeof(f)); rep(u, 1, n){ int k; cin >> k; rep(i, 1, k){ int v; cin >> v; f[u][v] = (i == 1 ? 0 : 1); } f[u][u] = 0; } rep(k, 1, n) rep(x, 1, n) rep(y, 1 ,n){ //floyd f[x][y] = min(f[x][y], f[x][k] + f[k][y]); } if(f[s][t] == INF) puts("-1"); else cout << f[s][t] << endl; }
07 POJ3255 Roadblocks
-
题目:无向图求 1 ∼ n 1\sim n 1∼n 的次最短路(第二短的路)
- 思路:求次最短路,需要每次更新时,一同更新记录此最短路(如果有的话)。这里用
d
i
j
k
s
t
r
a
dijkstra
dijkstra 堆优化在判断当前更新
tmpmindis
时,若小于最短路fdist[To]
则更新最短路,并将之前的最短路换为tmpmindis
(即swap(fdist[To], tmpmindis);
);此时若tmpmindis
大于最短路但小于次最短路,则更新次最短路。 - 原理:到 T o To To 点的次最短路是除最短路外一切路径的第二短。
//求次最短路 #include<iostream> #include<vector> #include<queue> #define rep(i, a, b) for(int i = a; i <= b; i++) #define per(i, a, b) for(int i = a; i >= b; i--) const int INF = 0x7fffffff; #define ll long long #define PII pair<int, int> const int N =1e5 + 5; //应该记为边数 using namespace std; int n, m, s; int vis[N], fdist[N], sdist[N]; vector<PII> vec[N]; priority_queue<PII, vector<PII>, greater<PII> > heap; void dijkstra(){ fdist[1] = 0, heap.push({0, 1}); while(heap.size()){ PII now = heap.top(); heap.pop(); int nod = now.second; for(auto j : vec[nod]){ int To = j.first, tmpmindis = j.second + now.first; if(fdist[To] > tmpmindis){ swap(fdist[To], tmpmindis); //更新最短路和当前次短路!!! heap.push({fdist[To], To}); } if(fdist[To] < tmpmindis && tmpmindis < sdist[To]){ sdist[To] = tmpmindis; heap.push({sdist[To], To}); } } } return; } int main(){ scanf("%d%d", &n, &m); rep(i, 1, m){ int x, y, z; scanf("%d%d%d", &x, &y, &z); vec[x].push_back({y, z}); vec[y].push_back({x, z}); } rep(i, 1, n) fdist[i] = sdist[i] = INF; dijkstra(); cout << sdist[n] << endl; } /* in 4 5 1 2 100 2 4 200 2 3 100 3 4 100 1 4 301 out 301 */
- 思路:求次最短路,需要每次更新时,一同更新记录此最短路(如果有的话)。这里用
d
i
j
k
s
t
r
a
dijkstra
dijkstra 堆优化在判断当前更新
08 UVA10389 Subway
-
思路:这道题难点在于处理输入数据,由于没有说明节点数量,只能通过整行输入来判定一组数据读取完成。用
stringstream
来处理整行字符串即可。- 问题处理很简单,一条线路上的相邻点之间是地铁速度,其他各点之间是走路速度,建立一个完全图在用 f l o y d floyd floyd 求起点到终点的最短路即可。
//注意构建边权 #include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> #define PDI pair<db, int> #define subway (40000.0 / 60.0) #define walk (10000.0 / 60.0) const db Pi = 3.141592653589793; const int INF = 0x3f3f3f3f; const int N = 2e2 + 5; const db eps = 1e-10; int cas, sx, sy, dx, dy; int cnt, line; db g[N][N]; struct AC{ db x, y; }node[N]; void add(int nx, int ny){ node[++cnt].x = nx, node[cnt].y = ny; } db dis(AC a, AC b){ return sqrt(pow((a.x - b.x), 2) + pow((a.y - b.y), 2)); } int main(){ cin >> cas; while(cas--){ cnt = 0; memset(g, 0, sizeof(g)); cin >> sx >> sy >> dx >> dy; add(sx, sy), add(dx, dy); string line; getline(cin, line); while(getline(cin, line)){ if(line.empty()) break; //读到空行,说明结束 stringstream ss(line); int x, y; ss >> x >> y; add(x, y); while(ss >> x >> y){ if(x == -1 && y == -1) break; add(x, y); g[cnt - 1][cnt] = g[cnt][cnt - 1] = dis(node[cnt - 1], node[cnt]) / subway; } } rep(i, 1, cnt) rep(j, i + 1, cnt){ //计算走路距离 if(!g[i][j]) g[i][j] = g[j][i] = dis(node[i], node[j]) / walk; } rep(k, 1, cnt) rep(i, 1, cnt) rep(j, 1, cnt){ //floyd g[i][j] = min(g[i][j], g[i][k] + g[k][j]); } printf("%d\n", (int)round(g[1][2])); if(cas) printf("\n"); } }
09 POJ3662 Telephone Lines
-
题目:在无向图上求一条 1 1 1 到 n n n 的路径,使路上第 k + 1 k + 1 k+1 大的边的边权最小
-
思路:由单调性和求最小值最小,想到二分答案。考虑是否存在一种合法的升级方案,使花费不超过 m i d mid mid,然后二分答案寻找尽量小的花费即可。
- 下面考虑怎样才表示方案合法:判断从 1 1 1 到 n n n 的所经过路径的最大边权是否不超过 k k k 即可
- 那么怎么判断合法:要找在
m
i
d
mid
mid 为最大值时成立的最短路,可以将边权
> mid
的边设为 1 1 1,最后最多只能用 k k k 个 ;<= mid
的边设为 0 0 0,表示可以随便用。跑一边 d i j k s t r a dijkstra dijkstra 就好啦。
#include<iostream> #include<algorithm> #include<cstring> #include<vector> #include<queue> #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define PII pair<int, int> #define pi acos(-1.0) const int INF = 0x3f3f3f3f; const int N = 1e3 + 5; const db eps = 1e-10; using namespace std; int n, m, k, g[N][N], maxn; vector<PII> eg[N]; int dist[N], vis[N]; int dij(int x){ //假设规定最大边权为 x rep(i, 1, n){ //预处理新边权 eg[i].clear(); //清空 rep(j, 1, n){ if(g[i][j] == 0) continue; if(g[i][j] > x) eg[i].push_back({j, 1}); //边权超限视为带权边,最多只能用 k 个 else eg[i].push_back({j, 0}); //边权未超限视为无权边,可以随便用 } } priority_queue<PII, vector<PII>, greater<PII> > heap; memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); dist[1] = 0, heap.push({0, 1}); //加入初始节点 while(heap.size()){ PII now = heap.top(); heap.pop(); int nod = now.second; if(vis[nod]) continue; vis[nod] = 1; for(int j = 0; j < eg[nod].size(); j++){ int To = eg[nod][j].first, distmp = eg[nod][j].second; if(dist[To] > now.first + distmp){ dist[To] = now.first + distmp; heap.push({dist[To], To}); } } } if(dist[n] == INF) return (!x ? 2 : 0); //到不了 return dist[n] <= k; //检查边数是否够用 } int main(){ cin >> n >> m >> k; memset(g, 0, sizeof(g)); rep(i, 1, m){ int u, v, w; cin >> u >> v >> w; g[u][v] = w, g[v][u] = w; maxn = max(w, maxn); //存下最大值 } if(dij(0) == 2){ //试试不花钱行不行 puts("-1"); return 0; } int l = 0, r = maxn, mid; while(l < r){ //二分寻找尽量小的花费,使升级方案合法(dist[n] <= k) mid = (l + r) / 2; if(dij(mid)) r = mid; //为了尽量小,若合法将 mid 缩小 else l = mid + 1; } cout << l << endl; }
10 UVA658 It’s not a Bug, It’s a Feature!
11 UVA11374 Airport Express
-
题目:在基础无向图上存在一些边权更小的快速边,要求最多经过一条快速边,求起点到终点的最短路。列出最短路路径并说明是否用到快速边,若用到请指明快速边起点。
-
思路:由于无法确定经过哪条快速边合适,可用 d i j k s t r a dijkstra dijkstra 先求出起点和终点到所有点的最短路。
- 若使用快速边
(
u
,
v
,
w
)
(u,v,w)
(u,v,w),所需答案便是
startdist[u] + w + enddist[v]
,即起点到达 u u u,经过快速边后, v v v 再到达终点。 - 判断使用快速边条件:当不用快速边无法从起点到达终点时;或使用快速边可缩短路程时。
- 列出路径:在用 d i j k s t r a dijkstra dijkstra 求最短路时,可记录到每个点的最短路上的前一个节点。这样当需要列出 ( s t a r t , e n d ) (start,end) (start,end) 的最短路时,只需要遍历一遍 e n d end end 的前每个节点,再输出。注意由于从终点出发的最短路需要以终点结尾,因此路径需要反转。
#include<bits/stdc++.h> #define rep(i, a, b) for(int i = a; i <= b; i++) #define per(i, a, b) for(int i = a; i >= b; i--) const int INF = 0x7fffffff; #define ll long long #define PII pair<int, int> const int N =5e2 + 5; using namespace std; int cas = 0, n, s, d, m, k; int change, x, y, z; ll minn, transferstart, transferend, transferval; ll vis[N], startdist[N], enddist[N], startpath[N], endpath[N]; vector<PII> vec[N]; void init(){ change = 0, transferstart = -1, transferend = -1; rep(i, 1, n){ vec[i].clear(); startdist[i] = enddist[i] = INF; startpath[i] = endpath[i] = -1; } } void dijkstra(int s, ll dist[], ll path[]){ memset(vis, 0, sizeof(vis)); priority_queue<PII, vector<PII>, greater<PII> > heap; dist[s] = 0, heap.push({0, s}); while(heap.size()){ PII now = heap.top(); heap.pop(); int nod = now.second; if(vis[nod]) continue; vis[nod] = 1; for(auto j : vec[nod]){ int To = j.first, distmp = j.second; if(dist[To] > now.first + distmp){ path[To] = nod; //记录路径 dist[To] = now.first + distmp; heap.push({dist[To], To}); } } } return; } void MIN(int x, int y, ll z){ if(minn > startdist[x] + enddist[y] + z){ change = 1; minn = startdist[x] + enddist[y] + z; transferstart = x, transferend = y, transferval = z; } } void printpath(int s, int d, ll path[], int Reverse){ //打印路径 stack<int> st, tmp, anst; while(st.size()) st.pop(); while(tmp.size()) tmp.pop(); while(anst.size()) anst.pop(); st.push(d); while(path[d] != -1){ st.push(path[d]); d = path[d]; } if(Reverse){ //反转 while(st.size()){ tmp.push(st.top()); st.pop(); } st = tmp; } while(st.size() > 1){ //末尾不能有空格 printf("%d ", st.top()); st.pop(); } printf("%d", st.top()); st.pop(); } int main(){ while(cin >> n >> s >> d){ init(); cin >> m; rep(i, 1, m){ cin >> x >> y >> z; vec[x].push_back({y, z}), vec[y].push_back({x, z}); } dijkstra(s, startdist, startpath); dijkstra(d, enddist, endpath); cin >> k; minn = INF; rep(i, 1, k){ cin >> x >> y >> z; MIN(x, y, z), MIN(y, x, z); } if(cas++) printf("\n"); //各组数据之间有空行 if(startdist[d] <= minn && startdist[d] != INF){ //无需商业线 printpath(s, d, startpath, 0); printf("\nTicket Not Used\n%d\n", startdist[d]); //直接输出起点到终点最短路 } else{ printpath(s, transferstart, startpath, 0); printf(" "); printpath(d, transferend, endpath, 1); //逆序 printf("\n%d\n%d\n", transferstart, minn); } } }
- 若使用快速边
(
u
,
v
,
w
)
(u,v,w)
(u,v,w),所需答案便是