传送门:(https://www.luogu.org/problemnew/show/P2661)
题目描述
有 n个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 ii 的同学的信息传递对象是编号为 T_i 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入输出格式
输入格式:
共2行。
第1行包含1个正整数 n ,表示 n 个人。
第2行包含 n 个用空格隔开的正整数 T_1,T_2,⋯⋯T_n。其中第 i 个整数 T_i
表示编号为 i的同学的信息传递对象是编号为 T_i 的同学,
输出格式:
11个整数,表示游戏一共可以进行多少轮。
输入样例1:
5
2 4 2 3 1
输出样例1:
3
说明
样例解释
游戏的流程如图所示。当进行完第 3轮游戏后, 4号玩家会听到 2 号玩家告诉他自己的生日,所以答案为 3。当然,第 3 轮游戏后, 2号玩家、3 号玩家都能从自己的消息来源得知自己的生日,同样符合游戏结束的条件。
对于 30%的数据, n ≤ 200;
对于 60%的数据, n ≤ 2500;
对于 100%的数据, n ≤ 200000。
分析
题非常简单,就是要求一个有向图中的最小环的边权总和。
由于图中每一条边权值都为1,而这个有向图里每一个人都只有1个信息传递的对象,也就是说每个点的出度都为1。由这一点不难发现这个环里点的数量等于边的数量。
值得一提的是,在这个有向图中,环必然是一个强连通分量,可以tarjan出来。
所以算法如下:
1.建图。
2.tarjan,注意原图可能由多个连通块组成,所以要跑一边全图。
3.找最小环中有多少节点。
代码:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
char ch;
while((ch=getchar())<'0'||ch>'9') ;
int ans=ch-48;
while((ch=getchar())>='0'&&ch<='9') ans=ans*10+ch-48;
return ans;
}
inline void write(int x){
if(x<0) putchar('-') ,x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n;
int head[200001],low[200001],dfn[200001],size[200001];
int scc[200001],vis[200001];
int cnt=0,num=0,color=0;
struct node{
int u,v,nt;
}e[200001];
inline void add(int u,int v){
cnt++;
e[cnt]=(node){u,v,head[u]};
head[u]=cnt;
}
stack<int>st;
inline void tarjan(int x){
low[x]=dfn[x]=++num;
st.push(x),vis[x]=1;
for(int i=head[x];i;i=e[i].nt){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]) low[x]=min(low[x],dfn[v]);
}
if(low[x]==dfn[x]){
color++;
while(1){
int t=st.top();st.pop();
vis[t]=0;scc[t]=color;
size[color]++;
if(t==x) break;
}
}
}
int main(){
n=read();
int v;
for(int i=1;i<=n;i++){
v=read();
add(i,v);
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
sort(size+1,size+color+1);
for(int i=1;i<=color;i++){
if(size[i]>1) {
write(size[i]);
break;
}
}
return 0;
}