洛谷 P2661 NOIP2015提高组 信息传递 题解【最小环】【dfs搜索】

作者: wjyyy 分类: 搜索,最小环,解题报告 发布时间: 2018-06-08 17:10

点击量:18

题意要求求出最短简单环。

题目描述

有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

个整数,表示游戏一共可以进行多少轮。 

输入输出样例

输入样例#1:
5
2 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;
}

 

说点什么

avatar
  Subscribe  
提醒
/* */