洛谷 P2661 NOIP2015提高组 信息传递 题解【最小环】【dfs搜索】
点击量:427
题意要求求出最短简单环。
题目描述
有n个同学(编号为1到n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为$ T_i$的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入输出格式
输入格式:
共2行。
第1行包含1个正整数n,表示n个人。
第2行包含n个用空格隔开的正整数 $ T_1,T_2,……,T_n $,其中第i个整数$ T_i$表示编号为的同学的信息传递对象是编号$ T_i$的同学, $ T_i \leq n$且$ T_i \neq i$。
输出格式:
个整数,表示游戏一共可以进行多少轮。
输入输出样例
输入样例#1:52 4 2 3 1输出样例#1:3【输入输出样例 1 说明】
游戏的流程如图所示。当进行完第 3 轮游戏后,4 号玩家会听到 2 号玩家告诉他自己的生日,所以答案为 3。当然,第 3 轮游戏后,2 号玩家、3 号玩家都能从自己的消息来源得知自己的生日,同样符合游戏结束的条件。
对于 30%的数据, n≤ 200;
对于 60%的数据, n≤ 2500;
对于 100%的数据, n≤ 200000。
数据规模是200000,如果从每个点出发做dfs/bfs显然不现实(轻轻松松$ N^2$ ),
事实证明暴力判used可以拿80分。
我做的方法是类似记忆化搜索的思路。对于一个点,如果搜过,那么它所在的环能更新的最小值已经被更新,就不去搜它。而每个点的出度为0,这样就没有很多冗余状况,而能保证当前解最优。
模仿tarjan,给每个遍历到的点打上一个时间戳,但不是dfs序,而是bfs序,也就是说,同层的节点的时间戳相同,因为在游戏进行过程中,他们所知道的消息是同时被传达的。所以当一个点被重复打上时间戳时,说明它传回了自己,将两个时间戳相减,就得到了游戏进行的轮数,将其最小值存下。
而由记忆化搜索的性质,每个点只会被遍历到一遍,所以时间复杂度是O(N)。同时只要从一个点出发,就一定会找到一个环,所以不用担心无限循环。
tips:为防止爆栈,而鉴于状态简单,我们直接用栈模拟dfs,效率也会提升不少。
还有一点,时间戳不能重复,如果有多段dfs,每段dfs的时间戳是单独从后面一点开始的。
Code:
#include<cstdio>
#include<cstring>
int ind[200200];
int dfn[200200];
int to[200200];
int minn=0x7fffffff;
int s[200200],top=0;
int cnt=0;
void dfs(int x,int t)
{
while(1)//直到找到被遍历过的点
{
if(dfn[to[x]])
{
if(dfn[to[x]]>=t)
{
if(dfn[x]+1-dfn[to[x]]<minn)
minn=dfn[x]+1-dfn[to[x]];
}
return;
}
else
{
dfn[to[x]]=dfn[x]+1;
if(dfn[to[x]]>cnt)
cnt=dfn[to[x]];
x=to[x];
}
}
return;
}
int main()
{
memset(dfn,0,sizeof(dfn));
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
to[i]=read();
ind[to[i]]++;
}
for(int i=1;i<=n;i++)//防止有多个“双连通分量”
if(ind[i]==0&&!dfn[i])
{
dfn[i]=++cnt;
dfs(i,cnt);
}
if(minn==0x7fffffff)
{
dfn[1]=++cnt;
dfs(1,cnt);
}
printf("%d\n",minn);//最小环
return 0;
}
说点什么