无向图的最小环问题

无向图的最小环问题

题目描述

给定一张无向图,求图中一个至少包含 $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/

posted @ 2023-04-14 17:27  onlyblues  阅读(288)  评论(0编辑  收藏  举报
Web Analytics