最小环的几种解法(并查集、删边)
题目: https://www.luogu.org/problemnew/show/P2661
题解:
原理:
1.如果有两个点祖先节点相同,那么就可以构成一个环,长度为两个点到祖先节点长度之和+1。
2.新加入的一条边的两个端点在并查集中同祖先,则一定成环 。
注意:图中不一定有一个环,即不一定是一个连通图,可能有好几个连通分量。
解法一:并查集+找爹函数传地址变量记录深度
#include<bits/stdc++.h>
using namespace std;
int n,f,i,k,cnt=0,ans=0x3f3f3f3f;//重要初始化
int fa[200005];
inline int find(int x,int &cnt)//找爹函数改进版
{//cnt要用地址来操作,因为是在递归中计数用的
cnt++;//层数更新
if(fa[x]==x) return x;
else return find(fa[x],cnt);
}
int main()
{
cin>>n;
for(i=1;i<=n;i++) fa[i]=i;//并查集的初始化
for(i=1;i<=n;i++)
{
cin>>f;//在本题中,这条路径是i->f
cnt=0;//重要的初始化
if(find(f,cnt)==i)//存在环路,可能=不止有一个环路
{
ans=min(cnt,ans);//维护最小的环的长度
//在使用min或max时,要注意维护变量的初始化
}
else
fa[i]=f;
}
cout<<ans<<endl;
return 0;
}
解法二:带权路径并查集
#include<bits/stdc++.h>
using namespace std;
int f[200002],d[200002],n,minn,last;
int find(int x){//带权路径并查集模板
if(f[x]==x) return x;
else
{
int last=f[x];//记录父节点
f[x]=find(f[x]);//更新祖先(开国太祖)节点
d[x]+=d[last];//更新路径长
return f[x];//不理解可以手推一遍并查集
}
}
void check(int a,int b){
int x=find(a),y=find(b);
if(x==y) minn=min(minn,d[a]+d[b]+1);
//新加入的一条边的两个端点在并查集中同祖先,则一定成环
//d[]保存该点到其祖先(开国太祖)的路径长度
else{
f[x]=y;//合并
d[a]=d[b]+1;//更新路径长度
}
}
int main(){
int i,t;
cin>>n;
for(i=1;i<=n;i++) f[i]=i;//并查集初始化
minn=0x7777777;//重要初始化
for(i=1;i<=n;i++){
cin>>t;
check(i,t);
}
cout<<minn<<endl;
return 0;
}
解法三:
原理:环中元素的入度等于出度。
#include<bits/stdc++.h>
using namespace std;
#define N 200005
#define INF 200005
int to[N],indegree[N];//记录入度
bool visit[N];
int n;
void delzero(){
bool flag=1;
for(int i=1;i<=n;i++){//仅仅是一轮删除
if(indegree[i]==0&&!visit[i]){
//以i为起点的边未被访问过,且i点入度为0,即这是一条废边
flag=0;//标志着还有边可以删
visit[i]=1;//标记删除的点,在search()中不会被访问
indegree[to[i]]--;//删除一条边
}
}
if(flag) return;//没有废边可以删除了
else delzero();//进行下一轮删除
}
int ans=INF;//重要初始化
void search(int start,int now,int step){
if(start==now){ans=min(ans,step);return;}//设置返回条件
visit[now]=1;//标记
search(start,to[now],step+1); //利用递归遍历环
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
cin>>to[i];
indegree[to[i]]++;//计算入度
}
delzero();
for(int i=1;i<=n;i++){
if(!visit[i]){//剪枝
visit[i]=1;//标记,认为一个连通块中至多有一个环
search(i,to[i],1);//遍历所有环
}
}
cout<<ans<<endl;
return 0;
}