无向图的最小环问题
无向图的最小环问题
题目描述
给定一张无向图,求图中一个至少包含 $3$ 个点的环,环上的节点不重复,并且环上的边的长度之和最小。该问题称为无向图的最小环问题。在本题中,你需要输出最小的环的边权和。若无解,输出 No solution. 。
输入格式
第一行两个正整数 $n,m$ 表示点数和边数。
接下来 $m$ 行,每行三个正整数 $u,v,d$,表示节点 $u,v$ 之间有一条长度为 $d$ 的边。
输出格式
输出边权和最小的环的边权和。若无解,输出 No solution. 。
样例 #1
样例输入 #1
5 7 1 4 1 1 3 300 3 1 10 1 2 16 2 3 100 2 5 15 5 3 20
样例输出 #1
61
提示
样例解释:一种可行的方案:$1-3-5-2-1$。
对于 $20\%$ 的数据,$n,m \leq 10$。
对于 $60\%$ 的数据,$m\leq 100$。
对于 $100\%$ 的数据,$1\le n\leq 100$,$1\le m\leq 5\times 10^3$,$1 \leq d \leq 10^5$。
无解输出包括句号。
解题思路
这里我们规定一个环中至少存在$3$个不同的点,如果一个环中只有两个点,我们不认为这是一个环。
通过集合的角度来思考,容易知道每一条边可能属于某一个环。因此可以枚举每一条边$(u,v)$,然后把这条边删掉,求$u \to v$的最短路(由于是无向图也可以求$v \to u$的最短路),那么包含边$(u,v)$的最小环长度就是$\text{dist}_{u,v}+w_{u,v}$。这是因为$u \to v$存在一条不含环的路径(否则就存在负环,即不存在最小环),因此在加上边$(u,v)$后就会构成一个环,且一定是包含边$(u,v)$的最小环。如果不是最短路径的话那么我们把这条路径变成最短路径,环的长度就会减小,因此包含边$(u,v)$的最小环一定由$u \to v$的最短路径构成。
而如果边权为非负的话那么可以用优先队列优化的Dijkstra来求最短路,时间复杂度为$O \left( m(n+m) \log{m} \right)$。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 110, M = 1e4 + 10, INF = 0x3f3f3f3f; 5 6 typedef pair<int, int> PII; 7 8 int head[N], e[M], wts[M], ne[M], idx; 9 struct Node { 10 int v, w, wt; 11 }edge[M]; 12 int dist[N]; 13 bool vis[N]; 14 15 void add(int v, int w, int wt) { 16 e[idx] = w, wts[idx] = wt, ne[idx] = head[v], head[v] = idx++; 17 } 18 19 int main() { 20 int n, m; 21 scanf("%d %d", &n, &m); 22 memset(head, -1, sizeof(head)); 23 for (int i = 0; i < m; i++) { 24 int v, w, wt; 25 scanf("%d %d %d", &v, &w, &wt); 26 add(v, w, wt), add(w, v, wt); 27 edge[i] = {v, w, wt}; 28 } 29 int ret = INF; 30 for (int u = 0; u < m; u++) { 31 memset(dist, 0x3f, sizeof(dist)); 32 dist[edge[u].v] = 0; 33 priority_queue<PII, vector<PII>, greater<PII>> pq; 34 pq.push({0, edge[u].v}); 35 memset(vis, 0,sizeof(vis)); 36 while (!pq.empty()) { 37 int t = pq.top().second; 38 pq.pop(); 39 if (vis[t]) continue; 40 if (t == edge[u].w) break; 41 vis[t] = true; 42 for (int i = head[t]; i != -1; i = ne[i]) { 43 if (t == edge[u].v && e[i] == edge[u].w || e[i] == edge[u].v && t == edge[u].w) continue; 44 if (dist[e[i]] > dist[t] + wts[i]) { 45 dist[e[i]] = dist[t] + wts[i]; 46 pq.push({dist[e[i]], e[i]}); 47 } 48 } 49 } 50 ret = min(ret, dist[edge[u].w] + edge[u].wt); 51 } 52 if (ret == INF) printf("No solution."); 53 else printf("%d", ret); 54 55 return 0; 56 }
再给出用Floyd求最小环的做法。
根据Floyd算法的性质知道,当最外层循环枚举到$k$时(尚未开始第$k$次循环),此时求得的是任意两点间只经过(编号)前$k-1$个点的最短路。因此我们可以根据环中最大节点的编号来对环进行分类,一共可以分成$n$类。由于最小环至少包含$3$个不同的点,假设环中最大的节点编号为$w$,与$w$相邻的两个节点为$u$和$v$。当枚举到$k=w$时,那么最小环的长度就是$\text{dist}_{u,v}+w_{v,w} + w_{w,u}$。
这是因为环中最大的节点编号为$w=k$,因此必然有$u < k$且$v < k$,又因为$u$和$v$是与$w$相邻的两点,因此环中必然包含边$(v,w)$和$(w,u)$,剩下的部分就是$u \to v$的最短路径,其中要保证这条最短路径所包含的点的编号不能超过$k$,否则就不满足$w$是环中编号最大的点。而根据Floyd算法的原理,当枚举到$k$时,此时的$\text{dist}_{u,v}$就是只经过前$k$个点(不包含$k$),$u \to v$的最短距离。因此我们只需枚举所有满足$u < k$且$v < k$的$u$和$v$,来得到当环中最大节点编号为$k$时的最小环。
AC代码如下,时间复杂度为$O(n^3)$:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 110, INF = 0x3f3f3f3f; 5 6 int g[N][N], f[N][N]; 7 8 int main() { 9 int n, m; 10 scanf("%d %d", &n, &m); 11 memset(g, 0x3f, sizeof(g)); 12 for (int i = 1; i <= n; i++) { 13 g[i][i] = 0; 14 } 15 while (m--) { 16 int v, w, wt; 17 scanf("%d %d %d", &v, &w, &wt); 18 g[v][w] = g[w][v] = min(g[v][w], wt); 19 } 20 memcpy(f, g, sizeof(g)); 21 int ret = INF; 22 for (int k = 1; k <= n; k++) { 23 for (int i = 1; i < k; i++) { 24 for (int j = 1; j < i; j++) { 25 ret = min(1ll * ret, 1ll * f[i][j] + g[i][k] + g[j][k]); 26 } 27 } 28 for (int i = 1; i <= n; i++) { 29 for (int j = 1; j <= n; j++) { 30 f[i][j] = min(f[i][j], f[i][k] + f[k][j]); 31 } 32 } 33 } 34 if (ret == INF) printf("No solution."); 35 else printf("%d", ret); 36 37 return 0; 38 }
参考资料
最小环 - OI Wiki:https://oi-wiki.org/graph/min-circle/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17317442.html