

本编的知识内容由小编手搓,以及资料均来自比特官网~
OJ题来源:洛谷
OJ题名:邮递员送信
OJ题归属:图论基础【单源最短路】
解题算法:邻接矩阵存图 + 算法四选一、这里用的是常规版dijkstra算法
💡经验总结:
话说是只需要 n - 1 循环即可求出 dist 数组的最终值,但是在这题里,如果在dijkstra算法里面求最终结果,只循环 n - 1 次是累加不出来最终正确的结果的,所以,我们最好循环 n 次哦!
这里还用了一个做题技巧,就是存反图,这里是从起点走到 i 位置,再从 i 位置走回到起点位置,然后在开始其他趟的进行,这里我们如果不建反图的话,解决从 i 位置走到起点位置是比较麻烦的,如果我们建反图,我们再跑一遍dijkstra算法就行了,这波操作就属于:将“从不同位置走到同一终点”问题转化成了“单源最短路问题”!
#include<iostream> #include<cstring> using namespace std; const int N = 1e3; int n, m; int e[N][N]; // 邻接矩阵存图 int dist[N]; bool st[N]; int ret; void dijkstra() { // 初始化 memset(dist, 0x3f, sizeof dist); memset(st, 0, sizeof st); dist[1] = 0; for (int i = 1; i <= n; i++) { int a = 0; for (int j = 1; j <= n; j++) if (!st[j] && dist[j] < dist[a]) a = j; st[a] = true; for (int b = 1; b <= n; b++) { int c = e[a][b]; if (dist[a] + c < dist[b]) dist[b] = dist[a] + c; } } } int main() { cin >> n >> m; memset(e, 0x3f, sizeof e); for (int i = 1; i <= m; i++) { int a, b, c; cin >> a >> b >> c; // 重边 e[a][b] = min(e[a][b], c); } dijkstra(); for (int i = 1; i <= n; i++) ret += dist[i]; for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) swap(e[i][j], e[j][i]); dijkstra(); for (int i = 1; i <= n; i++) ret += dist[i]; cout << ret << endl; return 0; }
OJ题来源:洛谷
OJ题名:采购特价商品
OJ题归属:图论基础【单源最短路】
解题算法:vector存图 + 算法四选一、这里用的是常规版dijkstra算法
---存图时要计算边权值,然后正常跑一下 dijkstra 算法。
💡经验总结:
这里的 dist 数组是 double 类型的,我们在给 dist 数组初始化的时候,不能再用memset进行 dist 数组的初始化了,要老老实实用 for 循环初始化~
memset是字节级填充工具,只适合字节类型、或 4 字节 int 特殊无穷大场景;double/long double遵循 IEEE754 浮点编码,逐字节填充无法得到预期数值,不能用 memset 初始化无穷大。 只有memset(arr, 0, sizeof(arr))可以安全清零浮点数组。#include<iostream> #include<vector> #include<cmath> using namespace std; typedef pair<int, double> PID; const int N = 110; int n, m; vector<PID> edges[N]; struct node { int x, y; }Coordinates[N]; // 存坐标 double dist[N]; bool st[N]; int tbegin, tend; void dijkstra() { // 初始化 for (int i = 0; i <= n; i++) dist[i] = 1e10; dist[tbegin] = 0; for (int i = 1; i <= n; i++) { // 找“min” int a = 0; for (int j = 1; j <= n; j++) if (!st[j] && dist[j] < dist[a]) a = j; st[a] = true; for (auto& e : edges[a]) { int b = e.first; double c = e.second; if (dist[a] + c < dist[b]) { dist[b] = dist[a] + c; } } } printf("%.2lf\n", dist[tend]); } int main() { cin >> n; for (int i = 1; i <= n; i++) { int x, y; cin >> x >> y; Coordinates[i].x = x; Coordinates[i].y = y; } cin >> m; for (int i = 1; i <= m; i++) { int u, v; cin >> u >> v; int x1 = Coordinates[u].x, y1 = Coordinates[u].y; int x2 = Coordinates[v].x, y2 = Coordinates[v].y; double x = x1 - x2; double y = y1 - y2; double w = sqrt(x * x + y * y); edges[u].push_back({ v, w }); edges[v].push_back({ u, w }); } cin >> tbegin >> tend; dijkstra(); return 0; }
OJ题来源:洛谷
OJ题名:拉近距离
OJ题归属:图论基础【单源最短路】
解题算法:可以解决负环的算法二选一、这里用的是 Bellman-Ford 算法
题目细节:
📌存边权存的是题目给的边权的相反数
📌爱情是双向的,以 1 为起点走到 n ;以 n 为起点走到 1 .
💡经验总结:
💡判断负环时,dist 数组已经更新好了,因为已经执行了 n - 1 轮,前面执行的 n - 1 轮都是为了第 n 轮判断负环~ 而前 n - 1 轮是可以把 dist 数组完全更新的。
#include<iostream> #include<vector> #include<cstring> using namespace std; typedef pair<int, int> PII; const int N = 1010; int n, m; vector<PII> edges[N]; int dist[N]; // BF 判断是否有负环,构造成带参的可以重复利用, // 判断负环不论结果,dist 数组已经是以起点 s 更新好的了 bool bf(int s) { // 初始化 memset(dist, 0x3f, sizeof dist); dist[s] = 0; bool flag; for (int i = 1; i <= n; i++) { flag = false; for (int a = 1; a <= n; a++) { for (auto& e : edges[a]) { int b = e.first, c = e.second; if (dist[a] + c < dist[b]) { dist[b] = dist[a] + c; flag = true; } } } if (flag == false) return flag; } return flag; } int main() { cin >> n >> m; for (int i = 1; i <= m; i++) { int s, t, w; cin >> s >> t >> w; edges[s].push_back({ t, -w }); } int ret; if (bf(1)) { cout << "Forever love" << endl; return 0; } ret = dist[n]; if (bf(n)) { cout << "Forever love" << endl; return 0; } ret = min(ret, dist[1]); cout << ret << endl; return 0; }
OJ题来源:洛谷
OJ题名:最短路计数
OJ题归属:图论基础【单源最短路】
解题算法:基于单元最短路算法实现的动态规划;由于要先确定一点已完成,且该题边权相等,可用最短路算法:bfs、dijkstra、spfa。如果边权不相等,可用最短路算法:dijkstra。本编实现了 bfs 与 堆优化版的dijkstra 两种。
题目细节:
📌重边要存
📌卡了输入输出那块的时间复杂度,要瘦身或者用 scanf/printf
📌bfs 与 dijkstra 两种实现时,对 st 数组有不同的要求,要应算法而变
💡经验总结:
💡这题的难点是动态规划的填表顺序,基于单源最短路的填表顺序,该题动态规划状态转移方程的更新是跟着松弛操作的判断同步的。dist[a] + 1 < dist[b] 的条件是第一次遍历到 b 点,而且 a 点已经确定了,dist[a] 的确定意味着 f[a] 的确定,f[b] = f[a];dist[a] + 1 == dist[b] 的条件是 不是第一次遍历到 b 点,最短路的条数要开始相加了,f[b] = f[a] + f[b]。
#include<iostream> #include<vector> #include<queue> #include<cstring> using namespace std; typedef pair<int, int> PII; const int N = 1e6 + 10, MOD = 100003; int n, m; vector<int> edges[N]; int dist[N]; bool st[N]; int f[N]; void bfs() { // 初始化 memset(dist, 0x3f, sizeof dist); f[1] = 1; dist[1] = 0; queue<int> q; q.push(1); // 不需要 st[i] = true ,因为这个点不只被遍历一次,打上标记就只能遍历一次了 while (q.size()) { auto a = q.front(); q.pop(); for (auto& e : edges[a]) { int b = e; if (dist[a] + 1 < dist[b]) { // 第一次遍历到 b dist[b] = dist[a] + 1; f[b] = f[a]; q.push(b); } else if (dist[a] + 1 == dist[b]) { // 不是第一次遍历到 f[b] = (f[a] + f[b]) % MOD; } } } } void dijkstra() { // 初始化 memset(dist, 0x3f, sizeof dist); f[1] = 1; dist[1] = 0; priority_queue<PII, vector<PII>, greater<PII>> heap; //小根堆 heap.push({ 0, 1 }); while (heap.size()) { auto p = heap.top(); heap.pop(); int a = p.second; if (st[a]) continue; // 已经确定过最短路的点就不用在更新一次了 // 不然,在 else if 那会多加 st[a] = true; for (auto& e : edges[a]) { int b = e; if (dist[a] + 1 < dist[b]) { dist[b] = dist[a] + 1; f[b] = f[a]; heap.push({ dist[b], b }); } else if (dist[a] + 1 == dist[b]) { f[b] = (f[a] + f[b]) % MOD; } } } } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m; for (int i = 1; i <= m; i++) { int x, y; cin >> x >> y; edges[x].push_back(y); edges[y].push_back(x); } //bfs(); dijkstra(); for (int i = 1; i <= n; i++) cout << f[i] << endl; return 0; }

2768

被折叠的 条评论
为什么被折叠?



