赞
踩
单源最短路径算法分类
/**
对应算法(n是顶点数,m是边数):
--单源最短路
--所有边权都是正数
--朴素Dijkstra算法 O(n^2) 适合于稠密图
--堆优化版的Dijkstra算法 O(m*log(n)) 适合于稀疏图
--存在负权边
--Bellman-Ford O(n*m) 适用于只选择不超过k条边的路径
--SPFA 一般是O(m), 最坏O(n*m) Bellman-Ford的优化
--多源汇最短路
-- Floyd算法 O(n^3)
*/
各种算法的原理
堆优化版的Dijkstra算法
在朴素版的Dijkstra基础上使用小根堆优化,每次可以直接从堆中取出最小值。此时操作(1)、(2)都变成O(1)的了,操作(3)之前相当于将每条边遍历了一次,一共执行m次,优化后在堆中修改一个数的时间复杂度是O(log(n)),因此最终的时间复杂度为:O(m*log(n))
Bellman_Ford算法
可以处理含有负权边的情况,同时还能判断出图中是否有负权回路,但是判断的代价比较大,一般不用该算法判断存在负权回路。我们常用spfa
算法判断负权回路。
spfa算法
要求图中不能存在负权环,一般99.9%的题目中都不存在负环。spfa
相当于对bellman_ford
算法进行优化。
问题描述
分析
朴素版的Dijkstra算法
来写。代码
#include <iostream> #include <cstring> using namespace std; const int N = 510; int n, m; int g[N][N]; // 邻接矩阵 int dist[N]; // 1号点到其他点的距离 bool st[N]; // 记录是否求出了最短路径 int dijkstra() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < n; i++) { // 选最小值 int t = -1; for (int j = 1; j <= n; j++) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; // 得到一个解 st[t] = true; // 更新距离 for (int j = 1; j <= n; j++) dist[j] = min(dist[j], dist[t] + g[t][j]); } if (dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { scanf("%d%d", &n, &m); memset(g, 0x3f, sizeof g); for (int i = 0; i < m; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); g[a][b] = min(g[a][b], c); } int t = dijkstra(); printf("%d\n", t); return 0; }
问题描述
分析
堆优化版的dijkstra算法
。代码
#include <iostream> #include <cstring> #include <queue> using namespace std; typedef pair<int, int> PII; const int N = 150010; int n, m; int h[N], e[N], w[N], ne[N], idx; int dist[N]; bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int dijkstra() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push({0, 1}); // (距离, 编号) while (heap.size()) { auto t = heap.top(); heap.pop(); int ver = t.second, distance = t.first; if (st[ver]) continue; st[ver] = true; for (int i = h[ver]; ~i; i = ne[i]) { int j = e[i]; // (i, j)这条边的权重为w[i] if (dist[j] > distance + w[i]) { dist[j] = distance + w[i]; heap.push({dist[j], j}); } } } if (dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); for (int i = 0; i < m; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } int t = dijkstra(); printf("%d\n", t); return 0; }
问题描述
分析
代码
#include <iostream> #include <cstring> using namespace std; const int N = 510, M = 10010; int n, m, k; int dist[N], backup[N]; // backup用于防止串联 struct Edge { int a, b, w; } edges[M]; int bellman_ford() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < k; i++) { memcpy(backup, dist, sizeof dist); for (int j = 0; j < m; j++) { int a = edges[j].a, b = edges[j].b, w = edges[j].w; dist[b] = min(dist[b], backup[a] + w); } } // 正无穷可能被更新变小,虽然还是表示正无穷 // 权值最小为-10000, 最多更新500次, 最多减少五百万 if (dist[n] > 0x3f3f3f3f / 2) return -1; return dist[n]; } int main() { scanf("%d%d%d", &n, &m, &k); for (int i = 0; i < m; i++) { int a, b, w; scanf("%d%d%d", &a, &b, &w); edges[i] = {a, b, w}; } int t = bellman_ford(); if (t == -1) puts("impossible"); else printf("%d\n", t); return 0; }
问题描述
问题链接:AcWing 851. spfa求最短路
分析
代码
#include <iostream> #include <cstring> #include <queue> using namespace std; const int N = 100010; int n, m; int h[N], e[N], w[N], ne[N], idx; int dist[N]; bool st[N]; // 表示某个点是否在队列中,保证每个点最多在队列中出现1次 void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int spfa() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; queue<int> q; q.push(1); st[1] = true; while (q.size()) { int t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q.push(j); st[j] = true; } } } } if (dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); for (int i = 0; i < m; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } int t = spfa(); if (t == -1) puts("impossible"); else printf("%d\n", t); return 0; }
问题描述
问题链接:AcWing 852. spfa判断负环
分析
代码
#include <iostream> #include <cstring> #include <queue> using namespace std; const int N = 2010, M = 10010; int n, m; int h[N], w[M], e[M], ne[M], idx; int dist[N], cnt[N]; // cnt[i]表示到虚拟源点的距离-1 bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } bool spfa() { queue<int> q; for (int i = 1; i <= n; i++) { // 相当于有一个虚拟源点,所有的点都要加入 q.push(i); st[i] = true; } while (q.size()) { int t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1; if (cnt[j] >= n) return true; if (!st[j]) { q.push(j); st[j] = true; } } } } return false; } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); for (int i = 0; i < m; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } if (spfa()) puts("Yes"); else puts("No"); return 0; }
问题描述
问题链接:AcWing 1129. 热浪
分析
代码
#include <iostream> #include <cstring> using namespace std; const int N = 2510, M = 6200 * 2 + 10; int n, m, S, T; int h[N], w[M], e[M], ne[M], idx; int dist[N], q[N]; // q为手写的循环队列 bool st[N]; // spfa算法中某个点是否已经在队列中 void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int spfa() { memset(dist, 0x3f, sizeof dist); dist[S] = 0; int hh = 0, tt = 0; q[tt++] = S; st[S] = true; while (hh != tt) { int t = q[hh++]; if (hh == N) hh = 0; st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q[tt++] = j; if (tt == N) tt = 0; st[j] = true; } } } } return dist[T]; } int main() { cin >> n >> m >> S >> T; memset(h, -1, sizeof h); for (int i = 0; i < m; i++) { int a, b, c; cin >> a >> b >> c; add(a, b, c), add(b, a, c); } cout << spfa() << endl; return 0; }
问题描述
问题链接:AcWing 1128. 信使
分析
朴素版的dijkstra算法
求解。代码
#include <iostream> #include <cstring> using namespace std; const int N = 110; int n, m; int g[N][N]; int dist[N]; bool st[N]; // 是否已经求出最短路径 int dijkstra() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < n; i++) { int t = -1; for (int j = 1; j <= n; j++) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; st[t] = true; for (int j = 1; j <= n; j++) dist[j] = min(dist[j], dist[t] + g[t][j]); } int res = 0; for (int i = 1; i <= n; i++) res = max(res, dist[i]); if (res == 0x3f3f3f3f) return -1; return res; } int main() { cin >> n >> m; memset(g, 0x3f, sizeof g); for (int i = 0; i < m; i++) { int a, b, c; cin >> a >> b >> c; g[a][b] = g[b][a] = c; } cout << dijkstra() << endl; return 0; }
问题描述
问题链接:AcWing 1127. 香甜的黄油
分析
分析题目可知这一题相当于:让我们确定一个起点,其他所有点到这个点的最短路径的值最小,输出这个起点。
这相当于让我们求解任意两点之间的最短路径。
这一题顶点数n最大为800,边数m最大为1450,考虑各种算法的时间复杂度(对于前三种算法,都需要再乘一个n):
/**
--朴素Dijkstra算法 O(n^3) = 512,000,000
--堆优化版的Dijkstra算法 O(n*m*log(n)) = 一千多万
--SPFA O(n*m) = 一百多万
-- Floyd算法 O(n^3) = 512,000,000
*/
分析可知,这一题可以使用堆优化版的Dijkstra算法
或者spfa
算法。
代码
#include <iostream> #include <cstring> using namespace std; const int N = 810, M = 3000, INF = 0x3f3f3f3f; int n, p, m; // n:奶牛数量; p:牧场数量; m:道路数量 int id[N]; // 每头牛所在的牧场编号 int h[N], e[M], w[M], ne[M], idx; int dist[N], q[N]; // q为循环队列 bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int spfa(int start) { memset(dist, 0x3f, sizeof dist); dist[start] = 0; int hh = 0, tt = 1; q[0] = start, st[start] = true; while (hh != tt) { int t = q[hh++]; if (hh == N) hh = 0; st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q[tt++] = j; if (tt == N) tt = 0; st[j] = true; } } } } int res = 0; for (int i = 0; i < n; i++) { int j = id[i]; // 第i头牛所在的牧场编号为j if (dist[j] == INF) return INF; res += dist[j]; } return res; } int main() { cin >> n >> p >> m; for (int i = 0; i < n; i++) cin >> id[i]; memset(h, -1, sizeof h); for (int i = 0; i < m; i++) { int a, b, c; cin >> a >> b >> c; add(a, b, c), add(b, a, c); } int res = INF; for (int i = 1; i <= p; i++) res = min(res, spfa(i)); cout << res << endl; return 0; }
问题描述
问题链接:AcWing 1126. 最小花费
分析
代码
#include <iostream> #include <cstring> using namespace std; const int N = 2010; int n, m, S, T; double g[N][N]; // 默认值为0,表示不连通 double dist[N]; bool st[N]; void dijkstra() { dist[S] = 1; for (int i = 0; i < n; i++) { int t = -1; for (int j = 1; j <= n; j++) if (!st[j] && (t == -1 || dist[j] > dist[t])) t = j; st[t] = true; for (int j = 1; j <= n; j++) dist[j] = max(dist[j], dist[t] * g[t][j]); } } int main() { scanf("%d%d", &n, &m); for (int i = 0; i < m; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); double z = (100.0 - c) / 100; g[a][b] = g[b][a] = z; } scanf("%d%d", &S, &T); dijkstra(); printf("%.8lf", 100 / dist[T]); return 0; }
问题描述
问题链接:AcWing 920. 最优乘车
分析
代码
#include <iostream> #include <cstring> #include <sstream> using namespace std; const int N = 510; int n, m; // n: 车站数量; m: 路线数量 bool g[N][N]; // 表示从一个站台能否直接到达另一个站台 int dist[N]; int stop[N]; // 读入时存储站台 int q[N]; void bfs() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; int hh = 0, tt = 0; q[0] = 1; while (hh <= tt) { int t = q[hh++]; for (int i = 1; i <= n; i++) if (g[t][i] && dist[i] > dist[t] + 1) { dist[i] = dist[t] + 1; q[++tt] = i; } } } int main() { cin >> m >> n; string line; getline(cin, line); // 读完前面的回车 while (m--) { getline(cin, line); stringstream ssin(line); int cnt = 0, p; while (ssin >> p) stop[cnt++] = p; for (int i = 0; i < cnt; i++) for (int j = i + 1; j < cnt; j++) g[stop[i]][stop[j]] = true; } bfs(); if (dist[n] == 0x3f3f3f3f) puts("NO"); else cout << max(dist[n] - 1, 0) << endl; return 0; }
问题描述
问题链接:AcWing 903. 昂贵的聘礼
分析
代码
#include <iostream> #include <cstring> using namespace std; const int N = 110, INF = 0x3f3f3f3f; int n, m; // n:物品数量; m:等级限制 int g[N][N], level[N]; int dist[N]; bool st[N]; // 可以交易的等级区间为[down, up] int dijkstra(int down, int up) { memset(dist, 0x3f, sizeof dist); memset(st, 0, sizeof st); dist[0] = 0; for (int i = 0; i <= n; i++) { // n + 1个点,循环n+1次 int t = -1; for (int j = 0; j <= n; j++) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; st[t] = true; for (int j = 0; j <= n; j++) if (level[j] >= down && level[j] <= up) dist[j] = min(dist[j], dist[t] + g[t][j]); } return dist[1]; } int main() { cin >> m >> n; memset(g, 0x3f, sizeof g); for (int i = 1; i <= n; i++) { int price, cnt; cin >> price >> level[i] >> cnt; g[0][i] = min(price, g[0][i]); // 0为虚拟源点 while (cnt--) { int id, cost; cin >> id >> cost; g[id][i] = min(g[id][i], cost); } } int res = INF; for (int i = level[1] - m; i <= level[1]; i++) // 枚举区间左端点 res = min(res, dijkstra(i, i + m)); cout << res << endl; return 0; }
下面四个题中的考点
问题描述
问题链接:AcWing 1135. 新年好
分析
优化版的dijkstra算法
求出车站1、a、b、c、d、e到其他所有点的单元最短路径;之后从1出发枚举所有可能到达亲戚的顺序,一共有5!
中情况,求出最小值即可。代码
#include <iostream> #include <cstring> #include <queue> #define x first #define y second using namespace std; typedef pair<int, int> PII; const int N = 50010, M = 200010, INF = 0x3f3f3f3f; int n, m; int h[N], e[M], w[M], ne[M], idx; int dist[6][N]; int source[6]; // 起点1、a、b、c、d、e的编号 bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } // 从start开始求单源最短路径,结果存入dist中 void dijkstra(int start, int dist[]) { memset(st, 0, sizeof st); memset(dist, 0x3f, N * 4); dist[start] = 0; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push({0, start}); // (距离, 节点编号) while (heap.size()) { auto t = heap.top(); heap.pop(); int ver = t.y, distance = t.x; if (st[ver]) continue; st[ver] = true; for (int i = h[ver]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[ver] + w[i]) { dist[j] = dist[ver] + w[i]; heap.push({dist[j], j}); } } } } // u:当前访问的亲戚的个数, 当前的起点是source[0], 当前的距离是distance int dfs(int u, int start, int distance) { if (u > 5) return distance; int res = INF; for (int i = 1; i <= 5; i++) if (!st[i]) { int next = source[i]; st[i] = true; // 当前考察dist[start]这个数组 res = min(res, dfs(u + 1, i, distance + dist[start][next])); st[i] = false; } return res; } int main() { scanf("%d%d", &n, &m); source[0] = 1; for (int i = 1; i <= 5; i++) scanf("%d", &source[i]); memset(h, -1, sizeof h); for (int i = 0; i < m; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c), add(b, a, c); } for (int i = 0; i < 6; i++) dijkstra(source[i], dist[i]); memset(st, 0, sizeof st); printf("%d\n", dfs(1, 0, 0)); return 0; }
问题描述
问题链接:AcWing 340. 通信线路
分析
相当于让我们找到一条线路,这条线路上第k+1大的边是这些可选线路中最小的,输出这条边上的值。
这一题可以使用二分来做,对于区间[0, 1000001]这个区间中的某个点x,定义性质如下:从1走到N,最少经过长度大于x的边的数量是否小于等于K。
假设答案是满足上面性质的最小的一个,记为res,则从1走到N,最少经过长度大于res的边的数量是否小于等于K,且res是最小的一个。对于任意x>=res,因为x变大了,所以上述性质都满足;对于x<res,如果x满足上述性质,则与res是满足性质的最小的一个矛盾。因此根据这个性质可以将区间分为两部分,具有两段性。
这里花费的取值范围是1~1000000,为什么我们要将区间取为[0, 1000001]呢?
取0是因为路径上的边数小于K,结果可能为0;
如果区间右端点取为 1 0 6 10^6 106,则如果图中边权全部为 1 0 6 10^6 106,最后结果会返回 1 0 6 10^6 106,如果1号点和N号点不连通,结果也会返回 1 0 6 10^6 106,我们无法区分这两种情况,因此右端点取 1 0 6 10^6 106+1,我们就可以区分这两种情况了。最终结果如果为 1 0 6 10^6 106+1,说明不连通。
代码
#include <iostream> #include <cstring> #include <deque> using namespace std; const int N = 1010, M = 20010; int n, m, k; int h[N], e[M], w[M], ne[M], idx; deque<int> q; int dist[N]; bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } // 从1走到N,经过长度大于bound的边的数量是否小于等于k // 返回true的话,说明bound还可以再减小 bool check(int bound) { memset(st, 0, sizeof st); memset(dist, 0x3f, sizeof dist); dist[1] = 0; q.push_back(1); while (q.size()) { auto t = q.front(); q.pop_front(); if (st[t]) continue; st[t] = true; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i], x = w[i] > bound; if (dist[j] > dist[t] + x) { dist[j] = dist[t] + x; if (!x) q.push_front(j); else q.push_back(j); } } } return dist[n] <= k; } int main() { cin >> n >> m >> k; memset(h, -1, sizeof h); while (m--) { int a, b, c; cin >> a >> b >> c; add(a, b, c), add(b, a, c); } int l = 0, r = 1e6 + 1; while (l < r) { int mid = l + r >> 1; if (check(mid)) r = mid; else l = mid + 1; } if (r == 1e6 + 1) puts("-1"); else cout << r << endl; return 0; }
问题描述
问题链接:AcWing 342. 道路与航线
分析
代码
#include <iostream> #include <cstring> #include <vector> #include <queue> #define x first #define y second using namespace std; typedef pair<int, int> PII; const int N = 25010, M = 150010, INF = 0x3f3f3f3f; int n, mr, mp, S; // 点数、道路数、航线数、起点 int id[N]; // 顶点所在的团,从1开始 int h[N], e[M], w[M], ne[M], idx; int dist[N]; // 从S出发的最短路径 int din[N]; // 每个团的入度 vector<int> block[N]; // 每个团包含的顶点 int bcnt; // 团的个数 bool st[N]; // dijkstra 中的判重数组 queue<int> q; // 拓扑排序需要的队列 void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } void dfs(int u, int bid) { id[u] = bid, block[bid].push_back(u); for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (!id[j]) dfs(j, bid); } } void dijkstra(int bid) { priority_queue<PII, vector<PII>, greater<PII>> heap; /** * 之前的题目只是放入了起点,因为能保证起点是开始的最小(dist==0),但是这道题 * 里面dijkstra是算每一个团里面的点距离s (最开始那个)的最短路,并不知道这 * 个团里面的起点是哪一个,所以当然要放全部团里面的点进入优先队列 */ for (auto u : block[bid]) heap.push({dist[u], u}); while (heap.size()) { auto t = heap.top(); heap.pop(); int ver = t.y, distance = t.x; if (st[ver]) continue; st[ver] = true; for (int i = h[ver]; ~i; i = ne[i]) { int j = e[i]; if (id[j] != id[ver] && --din[id[j]] == 0) q.push(id[j]); if (dist[j] > dist[ver] + w[i]) { dist[j] = dist[ver] + w[i]; if (id[j] == id[ver]) heap.push({dist[j], j}); } } } } void topsort() { memset(dist, 0x3f, sizeof dist); dist[S] = 0; for (int i = 1; i <= bcnt; i++) if (!din[i]) q.push(i); while (q.size()) { int t = q.front(); q.pop(); dijkstra(t); } } int main() { scanf("%d%d%d%d", &n, &mr, &mp, &S); memset(h, -1, sizeof h); // 读入道路 while (mr--) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c), add(b, a, c); } // 使用dfs求出每个团, 团的信息存储在id和block中 for (int i = 1; i <= n; i++) if (!id[i]) dfs(i, ++bcnt); // 读入航线 while (mp--) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); din[id[b]]++; } // 对团之间进行拓扑排序 topsort(); for (int i = 1; i <= n; i++) if (dist[i] > INF / 2) puts("NO PATH"); else printf("%d\n", dist[i]); return 0; }
问题描述
问题链接:AcWing 341. 最优贸易
分析
代码
#include <iostream> #include <cstring> using namespace std; const int N = 100010, M = 2000010; int n, m; int w[N]; // 商品在每个城市的价值 int hs[N], ht[N], e[M], ne[M], idx; // hs: 正向图; ht: 反向图 int dmin[N], dmax[N]; int q[N]; // 循环队列 bool st[N]; // spfa中的判重数组 void add(int h[], int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } // h: 邻接表; dist[k]: 1~k号点中买入的最小值 // type: type==0表示求dmin, type==1表示求dmax void spfa(int h[], int dist[], int type) { int hh = 0, tt = 1; if (type == 0) { memset(dist, 0x3f, sizeof dmin); // dist退化成指针,所以必须用dmin dist[1] = w[1]; q[0] = 1; } else { memset(dist, -0x3f, sizeof dmax); dist[n] = w[n]; q[0] = n; } while (hh != tt) { int t = q[hh++]; if (hh == N) hh = 0; st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (type == 0 && dist[j] > min(dist[t], w[j]) || type == 1 && dist[j] < max(dist[t], w[j])) { if (type == 0) dist[j] = min(dist[t], w[j]); else dist[j] = max(dist[t], w[j]); if (!st[j]) { q[tt++] = j; if (tt == N) tt = 0; st[j] = true; } } } } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", &w[i]); memset(hs, -1, sizeof hs); memset(ht, -1, sizeof ht); while (m--) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(hs, a, b), add(ht, b, a); if (c == 2) add(hs, b, a), add(ht, a, b); } spfa(hs, dmin, 0); spfa(ht, dmax, 1); int res = 0; for (int i = 1; i <= n; i++) res = max(res, dmax[i] - dmin[i]); printf("%d\n", res); return 0; }
问题描述
问题链接:AcWing 1137. 选择最佳线路
分析
代码
#include <iostream> #include <cstring> using namespace std; const int N = 1010, M = 21010, INF = 0x3f3f3f3f; int n, m, T; int h[N], e[M], w[M], ne[M], idx; int dist[N], q[N]; bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int spfa() { memset(dist, 0x3f, sizeof dist); dist[0] = 0; int hh = 0, tt = 1; q[0] = 0; while (hh != tt) { int t = q[hh++]; if (hh == N) hh = 0; st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q[tt++] = j; if (tt == N) tt = 0; st[j] = true; } } } } if (dist[T] == INF) return -1; return dist[T]; } int main() { while (scanf("%d%d%d", &n, &m, &T) != -1) { memset(h, -1, sizeof h); idx = 0; for (int i = 0; i < m; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } int s; scanf("%d", &s); while (s--) { int ver; scanf("%d", &ver); add(0, ver, 0); // 0为虚拟源点 } spfa(); printf("%d\n", spfa()); } return 0; }
问题描述
问题链接:AcWing 1131. 拯救大兵瑞恩
分析
代码
#include <iostream> #include <cstring> #include <deque> #include <set> #define x first #define y second using namespace std; typedef pair<int, int> PII; const int N = 11, M = N * N, E = 400, P = 1 << 10; int n, m, p, k; // p种门, k迷宫中门和墙的总数 int h[M], e[E], w[E], ne[E], idx; int g[N][N]; // 二维坐标映射为一维坐标,左上点编号定为1 int key[M]; // 每个位置钥匙的类型 int dist[M][P]; // 到每个点每个状态的最短距离 bool st[M][P]; // 判重数组 set<PII> edges; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } void build() { int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) for (int d = 0; d < 4; d++) { int x = i + dx[d], y = j + dy[d]; if (x <= 0 || x > n || y <= 0 || y > m) continue; int a = g[i][j], b = g[x][y]; if (edges.count({a, b}) == 0) add(a, b, 0); // 这里约定0可以直接通过 } } int bfs() { memset(dist, 0x3f, sizeof dist); dist[1][0] = 0; // 编号为1状态为0对应起点,距离起点距离为0 deque<PII> q; q.push_back({1, 0}); // (编号, 距离) while (q.size()) { auto t = q.front(); q.pop_front(); // cout << t.x << ' ' << t.y << endl; // break; if (st[t.x][t.y]) continue; st[t.x][t.y] = true; if (t.x == n * m) return dist[t.x][t.y]; if (key[t.x]) { // 当前位置有钥匙,对应边权为0 int state = t.y | key[t.x]; if (dist[t.x][state] > dist[t.x][t.y]) { dist[t.x][state] = dist[t.x][t.y]; q.push_front({t.x, state}); } } for (int i = h[t.x]; ~i; i = ne[i]) { int j = e[i]; if (w[i] && !(t.y >> w[i] - 1 & 1)) continue; // 有门但是没有对应的钥匙 if (dist[j][t.y] > dist[t.x][t.y] + 1) { dist[j][t.y] = dist[t.x][t.y] + 1; q.push_back({j, t.y}); } } } return -1; } int main() { cin >> n >> m >> p >> k; // 给每个位置一个一维编号 for (int i = 1, t = 1; i <= n; i++) for (int j = 1; j <= m; j++) g[i][j] = t++; memset(h, -1, sizeof h); // 读入墙和门 while (k--) { int x1, y1, x2, y2, c; cin >> x1 >> y1 >> x2 >> y2 >> c; int a = g[x1][y1], b = g[x2][y2]; edges.insert({a, b}), edges.insert({b, a}); if (c) add(a, b, c), add(b, a, c); } // 创建可以直接通过的边 build(); // 读取钥匙的位置 int s; cin >> s; while (s--) { int x, y, id; cin >> x >> y >> id; key[g[x][y]] |= 1 << (id - 1); // id从1开始, 为了节省空间,让编号从0开始 } cout << bfs() << endl; return 0; }
问题描述
问题链接:AcWing 1134. 最短路计数
分析
对于DP我们求解方案数,可以使用一个数组cnt记录方案数,根据状态转移更新cnt,DP类型的问题状态更新必须满足拓扑序。
对于图论求解方案数,我们必须得到一个这样的拓扑图,针对最短路径,我们需要得到最短路树。简单考虑,我们认为边的权值都是大于0 的。
求解单源最短路径的算法可以分为三大类:
(1)BFS:每次扩展一层,一定可以得到拓扑图。
(2)Dijkstra(包含双端队列广搜):每个点第一次出队的时候就确定最小值了,假设当前点u确定最短距离了,u就不可能再更新已经确定最短距离的点了(因为已经确定最短距离的点的距离一定小于u)。
(3)Bellman_ford(spfa):不一定满足拓扑序。因为某个点可能入队多次,出队多次,被更新距离的点也是不确定的。
另外再考虑一个问题:如果图中存在负权边,应该怎么办?
可以使用spfa求出最短路径;然后再把最短路径树建立出来;最后在这个树上统计最短路径次数就可以了
对于本题,因为所有边的权值都为1,使用BFS即可,对于某个点,如果能更新其距离,则更新,并将这个点的条数变成上一点对应的条数;如果说距离相等的话,距离不用更新,条数累加起来即可。
代码
#include <iostream> #include <cstring> using namespace std; const int N = 100010, M = 400010, mod = 100003; int n, m; int h[N], e[M], ne[M], idx; int dist[N], cnt[N]; // cnt[i]:1到i的最短路径条数 int q[N]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } void bfs() { memset(dist, -1, sizeof dist); dist[1] = 0, cnt[1] = 1; int hh = 0, tt = 0; q[0] = 1; while (hh <= tt) { int t = q[hh++]; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] == -1) { dist[j] = dist[t] + 1; cnt[j] = cnt[t]; q[++tt] = j; } else if (dist[j] == dist[t] + 1) { cnt[j] = (cnt[j] + cnt[t]) % mod; } } } } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while (m--) { int a, b; scanf("%d%d", &a, &b); add(a, b), add(b, a); } bfs(); for (int i = 1; i <= n; i++) printf("%d\n", cnt[i]); return 0; }
问题描述
问题链接:AcWing 383. 观光
分析
代码
#include <iostream> #include <cstring> #include <queue> using namespace std; const int N = 1010, M = 10010; struct Ver { // id: 节点编号 // type: 节点类型, 0代表最短路, 1代表次短路 // dist: 对应最短或者次短距离 int id, type, dist; bool operator> (const Ver &W) const { return dist > W.dist; } }; int n, m, S, T; int h[N], e[M], w[M], ne[M], idx; int dist[N][2], cnt[N][2]; bool st[N][2]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int dijkstra() { memset(st, 0, sizeof st); memset(dist, 0x3f, sizeof dist); memset(cnt, 0, sizeof cnt); dist[S][0] = 0, cnt[S][0] = 1; // 到S的最短路径为0,路径数目为1条 priority_queue<Ver, vector<Ver>, greater<Ver>> heap; heap.push({S, 0, 0}); while (heap.size()) { Ver t = heap.top(); heap.pop(); int ver = t.id, type = t.type, distance = t.dist, count = cnt[ver][type]; if (st[ver][type]) continue; st[ver][type] = true; for (int i = h[ver]; ~i; i = ne[i]) { int j = e[i]; if (dist[j][0] > distance + w[i]) { // 可以更新最小值 dist[j][1] = dist[j][0], cnt[j][1] = cnt[j][0]; // 更新次小值 heap.push({j, 1, dist[j][1]}); dist[j][0] = distance + w[i], cnt[j][0] = count; heap.push({j, 0, dist[j][0]}); } else if (dist[j][0] == distance + w[i]) { // 从t走到j也是最短路径 cnt[j][0] += count; } else if (dist[j][1] > distance + w[i]) { // 可以更新次小值 dist[j][1] = distance + w[i], cnt[j][1] = count; heap.push({j, 1, dist[j][1]}); } else if (dist[j][1] == distance + w[i]) { // 从t走到j也是次短路径 cnt[j][1] += count; } } } int res = cnt[T][0]; if (dist[T][0] + 1 == dist[T][1]) res += cnt[T][1]; return res; } int main() { int cases; scanf("%d", &cases); while (cases--) { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); idx = 0; while (m--) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } scanf("%d%d", &S, &T); printf("%d\n", dijkstra()); } return 0; }
问题描述
问题链接:AcWing 592. 雨
分析
class Solution { public: int trap(vector<int> &height) { int n = height.size(); if (n == 0) return 0; vector<int> left_max(n), right_max(n); left_max[0] = height[0]; for (int i = 1; i < n; i++) left_max[i] = max(left_max[i - 1], height[i]); right_max[n - 1] = height[n - 1]; for (int i = n - 2; i >= 0; i--) right_max[i] = max(right_max[i + 1], height[i]); int res = 0; for (int i = 0; i < n; i++) res += min(left_max[i], right_max[i]) - height[i]; return res; } };
dijkstea算法
求解该问题。dijkstea算法
求解该问题。**核心是证明当前从优先队列中出队的元素(假设是
(
x
,
y
)
(x, y)
(x,y))就是最小值,不可能被其他点更新了。**可以用反证法证明,假设当前队首元素会被更新成更小的数据,则一定是被队列中后面的点(假设是
(
i
,
j
)
(i, j)
(i,j))更新,但是从源点到后面的点的值更大,根据
d
i
s
t
[
x
]
[
y
]
dist[x][y]
dist[x][y]一定是被
m
a
x
(
d
i
s
t
[
i
]
[
j
]
,
h
[
x
]
[
y
]
)
max(dist[i][j], h[x][y])
max(dist[i][j],h[x][y]),这里是取大,所以当前队首元素不可能被更新的更小。代码
#include <iostream> #include <cstring> #include <queue> using namespace std; const int N = 55; int n, m; // 行数、列数 int h[N][N]; // 高度 int dist[N][N]; // dist[i][j]表示到达(i, j)的所有路径中边权最大值的最小值 bool st[N][N]; // 堆优化版的dijkstra算法中判重数组, 为true代表该点已经求出答案 int res; // 最终存储水的总量 struct Node { int x, y, d; // d: 到达(x, y)的所有路径中边权最大值的最小值 bool operator< (const Node &t) const { return d > t.d; // 默认大顶堆,需要使用小顶堆 } }; void dijkstra() { memset(dist, 0x3f, sizeof dist); memset(st, 0, sizeof st); res = 0; priority_queue<Node> heap; // 将四周的点加入优先队列 for (int i = 1; i <= n; i++) { heap.push({i, 1, h[i][1]}); dist[i][1] = h[i][1]; heap.push({i, m, h[i][m]}); dist[i][m] = h[i][m]; } for (int i = 2; i < m; i++) { heap.push({1, i, h[1][i]}); dist[1][i] = h[1][i]; heap.push({n, i, h[n][i]}); dist[n][i] = h[n][i]; } int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; while (heap.size()) { Node t = heap.top(); heap.pop(); if (st[t.x][t.y]) continue; st[t.x][t.y] = true; res += t.d - h[t.x][t.y]; for (int i = 0; i < 4; i++) { int x = t.x + dx[i], y = t.y + dy[i]; if (x < 1 || x > n || y < 1 || y > m) continue; if (dist[x][y] > max(t.d, h[x][y])) { // (t.x, t.y)到(x, y)的边权为h[x][y] dist[x][y] = max(t.d, h[x][y]); heap.push({x, y, dist[x][y]}); } } } } int main() { int T; scanf("%d", &T); for (int C = 1; C <= T; C++) { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", &h[i][j]); dijkstra(); printf("Case #%d: %d\n", C, res); } return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。