信息传递

题面:https://www.acwing.com/problem/content/519/

有 n 个同学(编号为 1 到 n)正在玩一个信息传递的游戏。

在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 TiTi 的同学。 

游戏开始时,每人都只知道自己的生日。

之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象)。

当有人从别人口中得知自己的生日时,游戏结束。

请问该游戏一共可以进行几轮?


题目意思很清楚,就是求最小环。先看样例

法1 :拓扑+电风扇(dfs)

很容易发现最小环是2->4->3->2

并且1和5不在环内。

观察到5没有前驱,而且经过拓扑之后1也是如此,图中就只剩下环

我们就从头到尾扫一遍。没有前驱就一定在环内,从这个点开始dfs遍历环内的点并标记

然后用环的大小更新答案

#include <bits/stdc++.h>
using namespace std;
const int maxn=2000009;
int indug[maxn],fa[maxn],vis[maxn],n;
queue<int>q;
void tuopu()
{
    for(int i=1;i<=n;i++)
        if(indug[i]==0)
            q.push(i);
    while(!q.empty())
    {
        int ans=q.front();q.pop();
        if(--indug[fa[ans]]==0)
            q.push(fa[ans]);
    }
}
int ans=99999999;
void dfs(int now,int qi,int s)
{
    if(fa[now]==qi){
        ans=min(ans,s);
        return;
    }
    if(vis[fa[now]])    return;
    vis[fa[now]]=1;
    dfs(fa[now],qi,s+1);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        indug[x]++;
        fa[i]=x;
    }
    tuopu();
    for(int i=1;i<=n;i++)
    {
        if(vis[i])    continue;
        if(indug[i]==0)    continue;
        vis[i]=1;
        dfs(i,i,0);
    }
    cout<<ans+1;
}
View Code

法2:拓扑+并查集

核心思路不变,仍然是一遍拓扑去掉不在环内的点

然后对于其余的点找祖先,把祖先的计数器加加

显然,环内的每一个点都会让祖先加1

最后扫一遍计数器不为0的点,取最小值

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <math.h>
#include <queue>
#include <map>
#include <stack>
#include <vector>
using namespace std;
int n;
int indug[200009];
int vis[200009];
vector<int>vec[200009];
void tuopu()
{
    queue<int>d;
    for(int i=1;i<=n;i++)    if(indug[i]==0)        vis[i]=1,d.push(i);
    while(!d.empty())
    {
        int ans=d.front();
        d.pop();
        for(int i=0;i<vec[ans].size();i++)
        {
            if(--indug[vec[ans][i]]==0)
                vis[vec[ans][i]]=1,d.push(vec[ans][i]);
        }
    }
}
int num[200009],pre[200009];
int cnt[200009];
int find(int x)
{
    if(pre[x]!=x)    pre[x]=find(pre[x]);
    return pre[x];
}
void join(int q,int w)
{
    pre[find(q)]=find(w);
    return;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)    pre[i]=i;
    for(int i=1;i<=n;i++)
    {
        int l;cin>>l;
        vec[i].push_back(l);
        indug[l]++;
        join(i,l);
    }
    tuopu();
    for(int i=1;i<=n;i++)
    {
        if(vis[i]==0)
            cnt[find(i)]++;
    }
    int ans=999999;
    for(int i=1;i<=n;i++)
        if(cnt[i]!=0)
            ans=min(ans,cnt[i]);
    cout<<ans; 
}
View Code

 法3:带权并查集

原理的话暂时也不太清楚。以后再来看吧。

#include<cstdio>
#include<iostream>
using namespace std;
int f[200002],d[200002],n,minn,last;   //f保存祖先节点,d保存到其祖先节点的路径长。 
int fa(int x)
{
    if (f[x]!=x)                       //查找时沿途更新祖先节点和路径长。 
    {
        int last=f[x];                 //记录父节点(会在递归中被更新)。 
        f[x]=fa(f[x]);                 //更新祖先节点。 
        d[x]+=d[last];                 //更新路径长(原来连在父节点上)。 
    }
    return f[x];
}
void check(int a,int b)
{
    int x=fa(a),y=fa(b);               //查找祖先节点。 
    if (x!=y) {f[x]=y; d[a]=d[b]+1;}   //若不相连,则连接两点,更新父节点和路径长。 
    else minn=min(minn,d[a]+d[b]+1);   //若已连接,则更新最小环长度。 
    return;
}
int main()
{
    int i,t;
    scanf("%d",&n);
    for (i=1;i<=n;i++) f[i]=i;         //祖先节点初始化为自己,路径长为0。 
    minn=0x7777777;
    for (i=1;i<=n;i++)
    {
        scanf("%d",&t);
        check(i,t);                    //检查当前两点是否已有边相连接。 
    }
    printf("%d",minn);
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/iss-ue/p/12487886.html