洛谷 P2661信息传递(图论/出度均为1的有向图最小环 带权并查集/拓扑排序/最小强连通分量)

题目

共有n(n<=2e5)个人,第i(1<=n<=个人)指向的人是ti

显然是个出度均为1的有向图,求该图的最小环的大小,输出大小

思路来源

https://www.cnblogs.com/SINXIII/p/10374689.html

题解

三种做法,

①强连通分量,上个tarjan板子,求最小size的,输出即可

②拓扑排序,注意到能清掉的点一定不在环上,

出度均为1,说明一个点一条边只能属于一个环,

清掉这些点之后每个环都是独立的,dfs即可

③带权并查集,dis[i]维护i到当前祖先的距离,

带权并查集的套路很固定,

(1)每次路径压缩时,刷新一下到当前根的距离dis,回溯更新

(2)合并根的时候,一定要去改当前 被合并的根 的dis值,改根方便后续的路径压缩

代码2(拓扑排序)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,in[N],x,u,t,q[N],to[N],ans;
bool vis[N]; 
vector<int>e[N];
void dfs(int u,int anc,int len){
	vis[u]=1;
	if(to[u]==anc){
		ans=min(ans,len);
		return;
	}
	if(!vis[to[u]]){
		dfs(to[u],anc,len+1);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d",&x);
		to[i]=x;
		in[x]++;
	}
	for(int i=1;i<=n;++i){
		if(!in[i])q[t++]=i;
	}
	for(int s=0;s<t;++s){
		u=q[s];
		vis[u]=1;//不可能出现在环里的点 
		if((--in[to[u]])==0){
			q[t++]=to[u];
		}
	}
	ans=n+1;
	for(int i=1;i<=n;++i){
		if(!vis[i]){
			dfs(i,i,1);
		} 
	}
	printf("%d",ans);
	return 0;
}

代码3(带权并查集)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,x,fa,par[N],dis[N],ans;
int find(int x){
	if(par[x]==x)return x;
	int fa=par[x];
	par[x]=find(par[x]);
	dis[x]+=dis[fa];
	return par[x];
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		par[i]=i;
	}
	ans=n+1;
	for(int i=1;i<=n;++i){
		scanf("%d",&x);
		fa=find(x);
		//i此时一定是一棵子树的根 因为i->x还没连 
		if(i==fa){
			ans=min(ans,dis[i]+dis[x]+1);
		}
		else{
			par[i]=fa;
			dis[i]=dis[x]+1;//i->x连一条边 i此时一定为根 后续会更新其余子树 
		}
	}
	printf("%d",ans); 
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值