最小环的几种解法(并查集、删边)

题目: 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;
}

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值