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