noip2015D1T1 信息传递(并查集判环)

题目描述

有 n 个同学(编号为 1 到 n ) 正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 ii的同学的 信息传递对象是编号为 Ti 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信 息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉 一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一 共可以进行几轮?

输入格式
输入共2行。 第1行包含1个正整数 n ,表示 n 个人。

第2行包含 n 个用空格隔开的正整数 T1,T2,⋯⋯,Tn ,其中第 i 个整数 Ti 表示编号为 i的同学的信息传递对象是编号为 Ti的同学, Ti≤n 且 Ti≠i 。

数据保证游戏一定会结束。

输出格式
输出共1行,包含1个整数,表示游戏一共可以进行多少轮。
n≤200000

根本没想过dfs就可以看出我对图论的熟悉程度真的很辣鸡了……
显然显然又显然,我们可以发现这是一个判断环的问题。
这里写图片描述
如图。
根据题意,同一连通块里是不可能出现两个环的(因为每个点出度只能为1)。然而这可能并不是一个连通图,所以仍然是要判断最小环的。
并查集。判环可直接完成,另记录环的长度即可。
是有向图噢///
以及,有向图中一点的父节点是它指向的那个点。

#include<bits/stdc++.h>
using namespace std;
int n;
int fa[200002],pre[200002];//fa存第i个点的祖先节点。pre存第i个点的父节点。实则用a数组也可以。但是不知道为什么这样比较快。
int a[200002];//第i个点的指向。
int minn=10000000;

inline void read(int &x){
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

inline int find_(int x)
{
    if(x==fa[x]) return x;
    return(fa[x]=find_(fa[x]));
}

int main()
{
    read(n);
    for(int i=1;i<=n;i++) 
    {
        read(a[i]);fa[i]=pre[i]=i;//第i个点的祖先和爹一开始都看作是自己。
    }
    for(int i=1;i<=n;i++)
    {
        int x=find_(i);//找到第i个点的祖先。
        int y=find_(a[i]);//第i个点指向的点的祖先。
        if(x==y)//如果他们的祖先是一样的,就说明它们在一个环中。具体为什么,看图……
        {
            int ans=1;
            for(int j=a[i];j!=i;j=pre[j]) ans++;//计算环的长度。for循环看着很玄学,简单地说就是从a[i]->a[i]的后继.....->直至重新回到i,其中i~a[i]这条边没算,所以ans初值是1.由于fa是经过状态压缩的,所以这里不能用它。
            minn=min(minn,ans);
        }
        fa[x]=y;//合并。
        pre[x]=a[i];//这就是,呃……就是这样。
    }
    cout<<minn;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值