有n个同学( 编号为1到n)正在玩一个信息传递的游戏。
在游戏里每人都有一个固定的信息传递对象,其中,编号为n的同学的信息传递对象是编号为Ti的同学。游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象( 注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象)。 当有人从别人口中得知自己的生日时, 游戏结束。 请问该游戏一共可以进行几轮?
输入格式
输入共2行。
第1行包含1个正整数n(2≤n≤200000),表示n个人。
第2行包含n个用空格隔开的正整数T1,T2,⋯,Tn,其中第i个整数Ti表示编号为i的同学的信息传递对象是编号为Ti的同学, Ti≤n且Ti≠i。数据保证游戏一定会结束。
对于50%的数据n≤2000。
输出格式
输出共1行,包含1个整数,表示游戏一共可以进行多少轮。
样例输入
5
2 4 2 3 1
样例输出
3
解题思路:根据题意先画一个消息传递的过程图,大概是这个样子:(利用题目的样例)
很容易能分析出,游戏每进行一轮,自己的信息都会沿着箭头走一步,当自己的信息传递到自己这里时,在图中就会形成一个环,所以这道题可以转化为,求有向图中的最小环问题,下面说一下利用带权并查集解决求最小环的思路;
在我的理解中,这道题利用并查集的过程大概是这个样子的:
1.先初始化,每个点都是自己的父节点
2.从第一个点开始,将样例中所说的点依次加入并查集
这里当进行到i=4的时候,4应该传递消息给3,把3加入并查集这时发现3在并查集,这时就找到了一个环,这个环的长度是3
3.删掉形成环的那一条边,继续寻找下一个环
4.遍历完所有的点,找到最小的环,此题答案为2中找到的环,答案为3
AC代码
#include<iostream>
#include<string>
using namespace std;
int f[200005],size[200005],a[200005];
int ans=2000000;
//寻找x根节点 同时进行路径压缩
int get(int x){
if(f[x]==x) return x;
int t=get(f[x]);
size[x]+=size[f[x]];
f[x]=t;
return f[x];
}
//合并两个并查集
void merge(int a,int b){
int ta=get(a);
int tb=get(b);
if(ta!=tb){
f[ta]=tb;
size[ta]=size[b]-size[a]+1;
}else{
ans=min(ans,size[b]+1);/*寻找最小的距离*/
}
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
f[i]=i;/*自己的父亲就是自己*/
size[i]=0;/*每个点到自己的距离为0*/
cin>>a[i];/*a数组存储的是同学i将要传递的给a[i]*/
}
for(int i=1;i<=n;i++){
merge(i,a[i]);//并查集加入(i,a[i])边
}
cout<<ans<<endl;
return 0;
}