洛谷题目链接:https://www.luogu.org/problemnew/show/2661
一道有很多种解法的题目
通过划归,发现就是求最小环
那么立即能想到的算法:1、Tarjan求强连通分量,最无脑
2、对于每个连通分量用Topo Sort,相当于剪去其他不在环上的边
不过用带权并查集也可以解决这道题目
首先发现只要一条边连接的两个点A、B在之前已经在一个集合中,则必定会形成一个环
那么难点就在于如何每次高效地求出环的大小
确实可以维护每个点到其根节点的距离,但每次merge操作要改变很多点的值
于是我们可以每次merge时只维护其到父节点的距离,不用有其他操作
而在find时再通过递归求出这个点到根节点的距离
Ex:有3个点A、B、C,f[A]=B,f[B]=C。在find前d[A]=cost(A,B),d[B]=cost(B,C),在递归时仅要d[A]+=d[f[A]]即可。
可以发现这样的处理和线段树中的lazy_tag运用了同样的思想
为了降低复杂度(只要不影响其他值的查找)在修改操作时不用对可能受影响的点全部进行修改,只维护最近点的数据,在查找操作时统一操作即可
Tips:1、在这样进行维护时,合并操作不能使用启发式合并,因为在查找时必须保证一条边两个节点间的父子关系不发生改变
2、n个点n-1条边绝不意味着只可能出现一个环,很可能有多个连通分量,从而出现多个环
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 int n,f[200005],d[200005]; 5 6 int find(int x) 7 { 8 if(f[x]!=x) //对d进行维护 9 { 10 int root=find(f[x]); 11 d[x]+=d[f[x]]; 12 return f[x]=root; 13 } 14 else return x; 15 } 16 17 int main() 18 { 19 cin >> n; 20 for(int i=1;i<=n;i++) f[i]=i,d[i]=0; 21 22 int res=1<<27; 23 for(int i=1;i<=n;i++) 24 { 25 int x;cin >> x; 26 int px=find(x),py=find(i); 27 28 if(px==py) res=min(res,d[i]+d[x]+1); 29 else f[i]=x,d[i]=1; //只能使用朴素合并方式 30 } 31 cout << res; 32 33 return 0; 34 }