【题目描述】
有 n 个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?
【输入格式】
共 2 行。
第 1 行包含1个正整数 n ,表示 n 个人。
第 2 行包含 n 个用空格隔开的正整数 T1,T2…Tn ,其中第 i 个整数 Ti 表示编号为 i 的同学的信息传递对象是编号为 Ti 的同学, Ti ≤n 且 Ti≠i 。
【输出格式】
1 个整数,表示游戏一共可以进行多少轮。
【样例输入】
5
2 4 2 3 1
【样例输出】
3
【题意分析】
这道题的解法很多,读完题目并分析后,我的第一反应是tarjan。传递关系可以看成一条有向边,因为要求的是有人听到别人告诉他自己的生日,这等价于在图中寻找一个不小于1的强联通分量。
tarjan板子题,Code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#define MAXN 200005
#define INF 1 << 29
using namespace std;
struct Front_Link_Star{
int next,to;
}edge[MAXN];
int head[MAXN],dfn[MAXN],low[MAXN],Circle[MAXN]/*,DAG[MAXN],*/,Stack[MAXN];
int top,tag,ans,cnt,circle,n;
bool vis[MAXN];
inline void Add_Edge(int u){
edge[++cnt].next=head[u];
head[u]=cnt;
}
inline int read(){
int s=0,w=1;
char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-')w=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;
}
inline void tarjan(int now){ //tarjan开始啦
dfn[now]=low[now]=++tag;
vis[now]=1;Stack[++top]=now;
for (register int i=head[now];i;i=edge[i].next){
int target=edge[i].to;
if (!dfn[target]){
tarjan(target);
low[now]=min(low[now],low[target]);
}else if (vis[target])low[now]=min(low[now],low[target]);
}
if (dfn[now]==low[now]){
int circle=0;
while (int y=Stack[top--]){
++circle;
vis[y]=0;
if (now==y)break;
}
if (circle>1)ans=min(ans,circle);
//如果找到了大于1的强联通分量,ans记录最小值即为答案
}
}
int main(){
n=read();
for (register int i=1;i<=n;i++){
edge[i].to=read();
Add_Edge(i); //加有向边
}
tag=top=0;
ans=INF;
for (register int i=1;i<=n;i++)
if (!dfn[i])tarjan(i);
printf("%d",ans);
return 0;
}
另外,这道题用Kruskal也可以做(说是Kruskal,其实就是并查集找环),初始化每一个点的父亲是他自己,加一条边就合并一下,如果发现当前点和要加的点父亲相同,就说明找到环了,暴力走一遍环路径取最小值即可。