引入
洛谷P6175 无向图的最小环问题
给定一张带权无向图,求图中至少包含 3 个点的环,环上节点不重复,并且环上所有边权值和最小。
解法
我们考虑对 Floyd 算法进行修改,找出最小环。
设最小环由节点 V1−V2−⋯−Vn 组成。
其中编号最大的节点为 Vm 与其相连的节点为 Vp,Vq。
则最小环可以表示为 Vp−{Vp,Vq之间不包含Vm的最短路}−Vq−Vm。
如下图:

这是可以证明的:
- 假设不是最短路:则最短路权值一定更小;
- 假设包含了 Vm:则出现了重复的节点。
在 Floyd 算法中,我们知道,枚举中转点到 k
时,此时数组内下标小于 k
的值保存的是 1~(k-1)
内部之间的最短路。
因此在 Floyd 算法中枚举 i,j 的循环前面再添加一个循环即可。G[i][k] + G[k][j] + dis[i][j]
即为这个环的权值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include<bits/stdc++.h> using namespace std;
const int N = 105; const int INF = 0x3f3f3f3f;
int G[N][N]; int dis[N][N];
int val=INF;
int main(){ memset(G,0x3f,sizeof G); memset(dis,0x3f,sizeof dis);
int n,m; scanf("%d%d",&n,&m); for(int i=0;i<m;i++){ int u,v,w; scanf("%d%d%d",&u,&v,&w); G[u][v] = G[v][u] = min(G[u][v],w); dis[u][v] = dis[v][u] = G[u][v]; }
for(int k=1;k<=n;k++){ for(int i=1;i<k;i++){ for(int j=1;j<i;j++){ if(dis[i][j] != INF && G[j][k] != INF && G[k][i] != INF){ if(dis[i][j] + G[j][k] + G[k][i] < val){ val = dis[i][j] + G[j][k] + G[k][i]; } } } }
for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(dis[i][j] > dis[i][k] + dis[k][j]){ dis[i][j] = dis[i][k] + dis[k][j]; } } } }
printf("%d\n",val); return 0; }
|
这份代码有几个点需要注意:
- 如果你的无穷大和我一样用的是
0x3f3f3f3f
,那么不能三个直接加起来,会爆炸,要先判断是否有无穷大。
- 在第 26~34 行的循环中,要注意满足 i,j<k 且 i=j。
求最小环上节点
和求 Floyd 最短路径同理,对于每两个节点,保存中转点即可。
保存中转点后,可以递归找出路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #include<bits/stdc++.h> using namespace std;
const int N = 105; const int INF = 0x3f3f3f3f;
int G[N][N]; int dis[N][N]; int mid[N][N];
vector<int> ansPath;
int val=INF; int a,b,c;
void find_path(int u,int v){ int md = mid[u][v]; if(!md)return;
find_path(u,md); ansPath.push_back(md); find_path(v,md); }
int main(){ memset(G,0x3f,sizeof G); memset(dis,0x3f,sizeof dis);
int n,m; scanf("%d%d",&n,&m); for(int i=0;i<m;i++){ int u,v,w; scanf("%d%d%d",&u,&v,&w); G[u][v] = G[v][u] = min(G[u][v],w); dis[u][v] = dis[v][u] = G[u][v]; }
for(int k=1;k<=n;k++){ for(int i=1;i<k;i++){ for(int j=1;j<i;j++){ if(dis[i][j] != INF && G[j][k] != INF && G[k][i] != INF){ if(dis[i][j] + G[j][k] + G[k][i] < val){ val = dis[i][j] + G[j][k] + G[k][i]; a = i, b = j, c= k; ansPath.clear(); find_path(a,b); } } } }
for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(dis[i][j] > dis[i][k] + dis[k][j]){ dis[i][j] = dis[i][k] + dis[k][j]; mid[i][j] = k; } } } }
printf("Min Circle: %d\n",val); printf("%d ",a); for(int x:ansPath)printf("%d ",x); printf("%d ",b); printf("%d\n",c);
return 0; }
|