dijkstra 求最小环( CCPC桂林 - E. Buy and Delete )
题意经过转化后,本质就是求最小环。
有向图有以下三种实现方式,而无向图只能用第一种实现方式。
- 实现方式1:删边求最短距离
- 有向图实现方式2:回到起点构成环
- 有向图实现方法3:任意两点间的最短距离
删边求最短距离
这种实现方法有向图无向图都可用。
- 如果是无向图必须用这种
- 如果是有向图边数和点数差不多是也可以使用。
对于一条有向边 ,可以删掉这条边(不用这条边更新)跑从 v 到 u 的最短距离,加上这条边的权值便是该边所在的最小环。
对于每条边都求一次最短路,所以时间复杂度:。
有向图代码
有向图不需要删除边 ,因为是求的 会自动忽略边 ;
#include <bits/stdc++.h> using namespace std; #define PII pair<int, int> #define pb push_back #define fi first #define se second #define endl '\n' const int N = 50010, mod = 1e9 + 7; int T, n, m, k; int a[N], c; int dist[N], f[N]; vector<PII> e[N]; int mina = 1e9; //最小环权值 struct node { int x, y, w; } b[N]; int dij(int st, int en) { priority_queue<PII, vector<PII>, greater<PII>> que; que.push({0, st}); for (int i = 1; i <= n; i++) dist[i] = 1e10, f[i] = 0; dist[st] = 0; while (que.size()) { int x = que.top().se; que.pop(); if (f[x]) continue; f[x] = 1; for (auto it : e[x]) { int tx = it.fi, w = it.se; if (dist[tx] > dist[x] + w) dist[tx] = dist[x] + w, que.push({dist[tx], tx}); } } } signed main() { Ios; cin >> n >> m; int ans = 0; for (int i = 1; i <= m; i++) { int x, y, z; cin >> x >> y >> z; e[x].pb({y, z}); b[i] = {x, y, z}; } for (int i = 1; i <= m; i++) { int x = b[i].x, y = b[i].y, w = b[i].w; dij(y, x); mina = min(mina, dist[x] + w); //从y到x的最短距离加上从x到y的这条边就是这个边所在的最小环 } cout << mina; return 0; }
无向图代码
有向图需要对边 进行continue处理
#include <bits/stdc++.h> using namespace std; #define PII pair<int, int> #define pb push_back #define fi first #define se second #define endl '\n' const int N = 50010, mod = 1e9 + 7; int T, n, m, k; int a[N], c; int dist[N], f[N]; vector<PII> e[N]; int pre[N]; struct node { int x, y, w; } b[N]; int dij(int st, int en) { priority_queue<PII, vector<PII>, greater<PII>> que; que.push({0, st}); for (int i = 1; i <= n; i++) dist[i] = 1e10, f[i] = 0, pre[i] = 0; dist[st] = 0; while (que.size()) { int x = que.top().se; que.pop(); if (f[x]) continue; f[x] = 1; for (auto it : e[x]) { int tx = it.fi, w = it.se; if (x == st && tx == en || x == en && tx == st) continue; // st-en这条边不能走 if (dist[tx] > dist[x] + w) dist[tx] = dist[x] + w, que.push({dist[tx], tx}), pre[tx] = x; } } } signed main() { cin >> n >> m; for (int i = 1; i <= m; i++) { int x, y, z; cin >> x >> y >> z; e[x].pb({y, z}); e[y].pb({x, z}); b[i] = {x, y, z}; } stack<int> stk; int mina = 1e9; for (int i = 1; i <= m; i++) { int x = b[i].x, y = b[i].y, w = b[i].w; dij(y, x); if (dist[x] + w < mina) { //记录最小环中的所有点 mina = dist[x] + w; while (stk.size()) stk.pop(); int t = x; while (t) stk.push(t), t = pre[t]; } } cout << mina << endl; if (!stk.size()) cout << "No solution."; while (stk.size()) cout << stk.top() << " ", stk.pop(); return 0; }
回到起点构成环
只能用于有向图,推荐使用这个,时间复杂度: 会比第一种速度快。
思想:
将每一个点都作为起点,跑 。
对于每个点,从该点出发判断是否能够再回到起点,如果能,说明从该点出发能构成环。
因为是跑最短路求得的,所以这个环是 所有从起点出发回到起点的环中 权值最小的一个。
将所有点作为起点所得的最小环的最小值 便是 整张图的最小环。
每个点都跑一遍 dij,所以 时间复杂度:。
有向图代码
#include <bits/stdc++.h> using namespace std; #define int long long #define PII pair<int, int> #define pb push_back #define fi first #define se second #define endl '\n' const int N = 200010, mod = 1e9 + 7; int T, n, m, k; int a[N], c; int dist[N], f[N]; vector<PII> e[N]; int mina = 1e18; //最小环大小 void dij(int st) { priority_queue<PII, vector<PII>, greater<PII>> que; que.push({0, st}); for (int i = 1; i <= n; i++) dist[i] = 1e9, f[i] = 0; dist[st] = 0; while (que.size()) { int x = que.top().se; que.pop(); if (f[x]) continue; f[x] = 1; for (auto it : e[x]) { int tx = it.first, w = it.second; if (tx == st) mina = min(mina, dist[x] + w); //下一个节点是起点,那么环的大小就是起点到当前点距离+到起点这条边的权值。 if (dist[tx] > dist[x] + w) dist[tx] = dist[x] + w, que.push({dist[tx], tx}); } } } signed main() { cin >> n >> m; for (int i = 1; i <= m; i++) { int x, y, z; cin >> x >> y >> z; e[x].pb({y, z}); } for(int i=1;i<=n;i++) dij(i); cout << mina; return 0; }
任意两点间的最短距离
对于每个点都跑一遍最短路,便得到任意两点间的最短距离。
那么从 x 点到 y 点的最短距离 + 从 y 点到 x 点的最短距离 便构成了一个最小环。
二重遍历所有组合,取所有环中的最小值便是整张图的最小环。
每个点都求一次最短路,所以时间复杂度:。
有向图代码
#include <bits/stdc++.h> using namespace std; #define PII pair<int, int> #define pb push_back #define fi first #define se second const int N = 5010, mod = 1e9 + 7; int T, n, m, k; int a[N], c; int dist[N][N], f[N]; vector<PII> e[N]; int dij(int st) { priority_queue<PII, vector<PII>, greater<PII>> que; que.push({0, st}); for (int i = 1; i <= n; i++) dist[st][i] = 1e10, f[i] = 0; dist[st][st] = 0; while (que.size()) { int x = que.top().se; que.pop(); if (f[x]) continue; f[x] = 1; for (auto it : e[x]) { int tx = it.fi, w = it.se; if (dist[st][tx] > dist[st][x] + w) dist[st][tx] = dist[st][x] + w, que.push({dist[st][tx], tx}); } } } signed main() { Ios; cin >> n >> m; for (int i = 1; i <= m; i++) { int x, y, z; cin >> x >> y >> z; e[x].pb({y, z}); } for (int i = 1; i <= n; i++) dij(i); int mina = 1e9; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { if (i != j) mina = min(mina, dist[i][j] + dist[j][i]); } cout << mina; return 0; }
E. Buy and Delete
方法1代码
因为没有学过dij求最小环,所以使用了第一种方法来求。结果被t疯了。后来面前通过剪枝通过。
代码
#include <bits/stdc++.h> using namespace std; typedef pair<int, int> PII; const int N = 2005, M = 5005; const int INF = 0x3f3f3f3f; int n, m, c; int h[N], w[M], e[M], ne[M], idx; // 邻接表存储所有边 int dist[N]; // 存储所有点到1号点的距离 bool st[N]; // 存储每个点的最短距离是否已确定 void add(int u, int v, int ww) { e[idx] = v; ne[idx] = h[u]; w[idx] = ww; h[u] = idx++; } int ans = INF; int dijkstra(int u, int v) { for (int i = 0; i <= n; i++) dist[i] = INF; for (int i = 0; i <= n; i++) st[i] = 0; dist[u] = 0; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push({0, u}); // first存储距离,second存储节点编号 while (heap.size()) { PII 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 != -1; i = ne[i]) { int j = e[i]; if (dist[j] > distance + w[i]) { dist[j] = distance + w[i]; if (dist[j] >= ans) continue; heap.push({dist[j], j}); } } } if (dist[v] == INF) return -1; return dist[v]; } signed main() { scanf("%d%d%d", &n, &m, &c); for (int i = 0; i <= n; i++) h[i] = -1; int mi = INF; for (int i = 0; i < m; i++) { int u, v, w; scanf("%d%d%d", &u, &v, &w); add(u, v, w); mi = min(w, mi); } if (mi > c) return puts("0"), 0; for (int i = 1; i <= n; i++) { for (int j = h[i]; j != -1; j = ne[j]) { int sum = dijkstra(e[j], i); /*重要剪枝*/ if(w[j]>=c) continue; if (sum == -1) continue; ans = min(ans, sum + w[j]); } } if (ans <= c) puts("2"); else puts("1"); }
方法2代码
#include <bits/stdc++.h> using namespace std; typedef pair<int, int> PII; const int N = 2005, M = 5005; const int INF = 0x3f3f3f3f; int n, m, c; int h[N], w[M], e[M], ne[M], idx; // 邻接表存储所有边 int dist[N]; // 存储所有点到1号点的距离 bool st[N]; // 存储每个点的最短距离是否已确定 bool vis[N][N]; void add(int u, int v, int ww) { e[idx] = v; ne[idx] = h[u]; w[idx] = ww; h[u] = idx++; } int ans = INF; void dijkstra(int u) { for (int i = 0; i <= n; i++) dist[i] = INF; for (int i = 0; i <= n; i++) st[i] = 0; dist[u] = 0; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push({0, u}); // first存储距离,second存储节点编号 while (heap.size()) { PII t = heap.top(); heap.pop(); int ver = t.second, distance = t.first; if (st[ver]) continue; st[ver] = true; // cout<<ver<<endl; for (int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; if (j == u) ans = min(ans, distance + w[i]); //下一个节点是起点,那么环的大小就是起点到当前点距离+到起点这条边的权值。 if (dist[j] > distance + w[i]) { dist[j] = distance + w[i]; heap.push({dist[j], j}); } } } } signed main() { scanf("%d%d%d", &n, &m, &c); for (int i = 0; i <= n; i++) h[i] = -1; int mi = INF; for (int i = 0; i < m; i++) { int u, v, w; scanf("%d%d%d", &u, &v, &w); add(u, v, w); mi = min(w, mi); } if (mi > c) return puts("0"), 0; for (int i = 1; i <= n; i++) { dijkstra(i); } // cout<<ans<<endl; if (ans <= c) puts("2"); else puts("1"); }
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/16817157.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步