AcWing 观光之旅
Description
给定一张无向图,求图中一个至少包含3个点的环,环上的节点不重复,并且环上的边的长度之和最小。
该问题称为无向图的最小环问题。
你需要输出最小环的方案,若最小环不唯一,输出任意一个均可。
Input
第一行包含两个整数N和M,表示无向图有N个点,M条边。
接下来M行,每行包含三个整数u,v,l,表示点u和点v之间有一条边,边长为l。
Output
- 输出占一行,包含最小环的所有节点(按顺序输出),如果不存在则输出’No solution.’。
Sample Input
5 7 1 4 1 1 3 300 3 1 10 1 2 16 2 3 100 2 5 15 5 3 20
Sample Output
1 3 5 2
Data Size
- 1≤N≤100,
1≤M≤10000,
1≤l<500
题解:
- Floyd。
- 无向图求最小环用Floyd,有向图求最小环用Dijkstra。
- 我们知道,当Floyd做最短路时,枚举到k时,dis(i, j)表示的是i到j的最短距离(途中只走小于k的点)。那么我们就可以以k为环的中心。然后枚举两个点i, j表示k的左边点和右边点。那么这个环的权值就是dis(i, j) + a(i, k) + a(i, j),因为dis(i, j)能算出来是通过只走<k的点走出来的。所以i到j的最短路并没有与i -> k与j -> k的路径重合。因此是个最短路。
- 所以枚举k,k从1-n。这样就可以求出每种k情况的最小环。在这些最小环里取最小即可。
- 算法流程:
- 以k做中心,算出当前k为中心的最小环。
- 将k加入“可走点”,进行松弛操作。
- 代码细节:当数据类型是longlong时,用memset(0x3f)就为longlong最大值的一半,并不是int最大值的一半。
#include <iostream> #include <cstdio> #include <cstring> #include <vector> #define N 105 using namespace std; int n, m, ans = 0x3f3f3f3f; int dis[N][N], a[N][N], mid[N][N]; vector<int> path; void getPath(int x, int y) { if(!mid[x][y]) return; getPath(x, mid[x][y]); path.push_back(mid[x][y]); getPath(mid[x][y], y); } int main() { cin >> n >> m; memset(a, 0x3f, sizeof(a)); memset(dis, 0x3f, sizeof(dis)); for(int i = 1; i <= n; i++) dis[i][i] = a[i][i] = 0; for(int i = 1; i <= m; i++) { int u, v, w; cin >> u >> v >> w; a[u][v] = a[v][u] = min(w, a[u][v]); dis[u][v] = dis[v][u] = a[u][v]; } for(int k = 1; k <= n; k++) { for(int i = 1; i < k; i++) for(int j = i + 1; j < k; j++) if((long long)dis[i][j] + a[i][k] + a[k][j] < ans) { ans = dis[i][j] + a[i][k] + a[k][j]; path.clear(); path.push_back(i); getPath(i, j); path.push_back(j); path.push_back(k); } for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) if(i != j && j != k && i != k) if(dis[i][k] + dis[k][j] < dis[i][j]) { dis[i][j] = dis[i][k] + dis[k][j]; mid[i][j] = k; } } if(ans == 0x3f3f3f3f) cout << "No solution."; else for(int i = 0; i < path.size(); i++) cout << path[i] << ' '; return 0; }