对于一个图,如何求出其中的最小环(不包括一元环)?
很显然,对于一个无向图,每一条边都是一个二元环;对于有向图,可以考虑从每一个点出发,用DFS求出它到自己的距离,如果遍历了$N$个点仍未便利到自己,则该点不在环中。但是这样做的复杂度非常感人……如果没有分析错的话最坏应该是$O(n^2 m)$
可以考虑有一些优化, 对于一个点,如果它在一个环中,那么它在该环上dfs过的点一定在环中。在回溯一个点的过程中,如果搜到环,那么在搜下一个点的时候就不清空他们的标记数组。对于搜不到环的点,它的入度一定为一,如果不为一,那么删除它入边所连的入度为零的点后入度为零。所以可以预处理,删除入度为零的点和它所连的边(如果后面还有用就标记),然后再按照上述方式dfs,时间复杂度为$O(nm)$
但是对于稠密图,复杂度还是太高,甚至高过$O(n^3)$,这听起来是不是有些耳熟? 没错,Floyd。
用 $k$ 作为当前搜索点,不断求出其它点的最短路径和路经所在环长,即
$dis[i][j] = std::max(dis[i][j], dis[i][k], dis[j][k]), ans = std::max(ans, dis[i][j]+G[k][i]+G[j][k]); // G[][] 为邻接矩阵$
时间复杂度为 $O(N^3)$
当然,在$N>1000$的稠密图中,这种方法的复杂度依然很感人。
由于连边的关系也可以看作是树中“父与子”的关系,则可以使用并查集,通过路径压缩求出在某一图中的环,若存在两个连边的点在同一并查集中,那么一定有环,环的权值为两点到祖先的权值和加1。
为什么这样是对的呢? 在一个路径已经被压缩的树中,任意两节点在图中一定相连,当某一点的父节点成为该点的子节点时,该点一定在环上。只要没有重边,那么该方法一定适用。
Floyd 代码:
int G[N][N], dis[N][N]; int main(){ for(int k = 1; k <= n; k++){ for(int i=1;i<=k-1;i++) for(int j=i+1;j<=k-1;j++) ans = std::max(ans, dis[i][j]+G[k][i]+G[k][j]); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j] = std::max(dis[i][j], G[i][k]+G[k][j]); } //... return 0; }
并查集代码
1 int find(int x){ 2 if(f[x] != x){ 3 int o = f[x]; 4 f[x] = find(f[x]); 5 d[x] += d[o]; 6 } 7 return f[x]; 8 } 9 int main(){ 10 scanf("%d", &n); 11 for(int i=1;i<=n;i++) f[i] = i; 12 for(int i=1;i<=n;i++){ 13 scanf("%d", &x); 14 int a = find(i), b = find(x); 15 if(a != b) { f[a] = b; d[i] = d[x]+1; } 16 else min = std::min(min, d[x] + d[i] + 1); 17 } 18 printf("%d " ,min); 19 return 0; 20 }
来写个裸题练练吧!https://www.luogu.org/problemnew/show/P2661