题目描述
有 nnn 个同学(编号为 111 到 nnn )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 iii 的同学的信息传递对象是编号为 TiT_iTi 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入输出格式
输入格式:
共222行。
第111行包含1个正整数 nnn ,表示 nnn 个人。
第222行包含 nnn 个用空格隔开的正整数 T1,T2,⋯⋯,TnT_1,T_2,\cdots\cdots,T_nT1,T2,⋯⋯,Tn ,其中第 iii 个整数 TiT_iTi 表示编号为 iii 的同学的信息传递对象是编号为 TiT_iTi 的同学, Ti≤nT_i \leq nTi≤n 且 Ti≠iT_i \neq iTi≠i 。
输出格式:
111个整数,表示游戏一共可以进行多少轮。
输入输出样例
输入样例#1:
5
2 4 2 3 1
输出样例#1:
3
说明
样例1解释
游戏的流程如图所示。当进行完第3 33 轮游戏后, 44 4号玩家会听到 222 号玩家告诉他自己的生日,所以答案为 333。当然,第 333 轮游戏后,2 2 2号玩家、 333 号玩家都能从自己的消息来源得知自己的生日,同样符合游戏结束的条件。
对于 30%30\%30%的数据, n≤200n ≤ 200n≤200;
对于 60%60\%60%的数据, n≤2500n ≤ 2500n≤2500;
对于100% 100\%100%的数据, n≤200000n ≤ 200000n≤200000。
—————————————————————————分割线———————————————————————————
观察完这个题目,我发现这个问题可以用图论的知识来解决。
大前提:
我们把每个人看作一个结点,信息的传递用有向线段来表示。
问题转化:
1.如果某个人的生日信息经过若干次传递后传回自己,那么通过将结点用有向线段连接起来的方式我们可以得到一个环。
2.游戏终止条件是只要有人得知自己生日信息就结束,就相当于我们要找到所有图中最小的环,也即节点数最少的环。
3.从某个结点开始用有向线段依次连接,最终形成的图有两种:A.一条链连接在一个环上的图。B.仅含一个环的图。
解决措施:
1.用搜索的方法,从每个结点依次搜索,并记录每次搜索的结点,当在本次搜索时遇到本次已经搜索过的结点时,说明此时已经形成环,而传递次数就可以转化为第二次搜索到该结点时已搜索的结点数减去本次搜索中第一次搜索到该结点时的已搜索节点数。
2.如果采取依次暴力搜索这两种图的方式,很容易超时。这时我们就要考虑优化方法,我思考了一下发现,如果该结点在已搜索的图中出现过,我们就可以不搜索这个结点,因为从该结点开始搜索所形成的图必定是已经搜索过的图所生成的子图。
大家注意了,敲黑板啦,接下来就是最重要的代码部分了!!!
#include<bits/stdc++.h>
int min(int a,int b)
{
return a>b?b:a;
}
int visit[1000001],visited[1000001]; //visit数组用来标记本次搜索过程中的结点
int turn[1000001]; //visited数组用来标记本次搜索之前已经搜索过的结点
int bs[1000001]; //turn数组用来存储该结点所指向的下一个结点,bs数组用来存储搜索到
int ans=9999999; //该结点时已经搜索的结点数,以便求出环的有向线段数,即传递次数
//将结果初始化为一个充分大的数,防止产生意想不到的结果
void dfs(int node,int sum) //node表示指当前搜索的结点
{ //sum是指本次搜索中已搜索的节点数
if(visited[node]==1) //如果这个结点在本次搜索之前已经搜索过,那么
return; //本次搜索形成的图为某个已搜索过的图的子图,故不用
if(visit[node]==1) //再搜索下去,因为该图必定是最优解的同解或不是最优解
{
ans=min(ans,sum-bs[node]); //如果本次搜索已经搜索过该结点,那么此
} //时形成了环,传递信息次数就等于第二次
else //搜索到该结点时已搜索的结点数减去第一
{ //次搜索到该结点时已搜索的结点数
visit[node]=1;
bs[node]=sum; //如果本次搜索过程没搜索过此结点,那么标记此
dfs(turn[node],sum+1); //结点为已搜索过,并将搜索到该结点时已搜索过
visited[node]=1; //的结点数存到bs数组中的相应位置,然后进行下
} //一步搜索,并且在搜索完时将本次搜索中所涉及
} //到的所有结点标记为已搜索过,也就是为了避免
int main() //搜索子图所带来的时间上的浪费
{
int n,i;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&turn[i]); //读入有向线段到turn数组当中
for(i=1;i<=n;i++)
dfs(i,0); //依次从每个结个结点开始搜索以该结点为起始点的图
printf("%d",ans);
}