题目链接:
https://www.luogu.org/problem/P2921
算法:
tarjan
注意:
1:low[maxn]存:同一个连通分量中可以回到的节点的最小时间戳,即dfn的值
思路:
1:这道题目的本质就是tarjan算法
2:如果开始时这个节点就是在一个节点数大于2的环中,即一个连通分量中那么所要求的间隔数ans[i]就等于这个连通分量所包含的节点个数
3:如果这个节点的下一个节点是在一个环中,那么
if(ans[next[u]]) return ans[u]=ans[next[u]]+1;
4:如果一个节点的下一个节点不是在一个环中,则递归调用findans函数
else return ans[u]=findans(next[u])+1;
5:如果这个节点是自环,就是自己到达自己,那么
if(next[u]==u) return ans[u]=1;
6:2是提前处理的,3,4,5是一个递归函数,注意一定要把一开始就是在一个环中的节点处理之后再循环调用findans函数
7:1:color[maxn]存:每一个节点所处的连通分量的编号,可以理解为每一个节点染色的颜色
2:colorcnt记录此时的连通分量的编号,即此时要染色的颜色
3:colornum[maxn]存:每一个编号的连通分量所包含的节点个数,即每一种颜色的节点的个数,即每一个环,每一个连通分量所包含的节点个数
#include <bits/stdc++.h>
using namespace std;
//struct edge{
//int to,val;
//};
//vector<int>g[maxn];
const int maxn=1e5+5;
int next[maxn];
int dfn[maxn],low[maxn],timing;
bool ins[maxn];
int color[maxn],colornum[maxn],colorcnt;
stack<int>s;
int ans[maxn];
int findans(int u){
if(next[u]==u) return ans[u]=1;
if(ans[next[u]]) return ans[u]=ans[next[u]]+1;
else return ans[u]=findans(next[u])+1;
}
void tarjan(int u){
timing++;
dfn[u]=low[u]=timing;
s.push(u);
ins[u]=true;
// for(int v:g[u]){
int v=next[u];
if(dfn[v]==0){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(ins[v]==true){
low[u]=min(low[u],dfn[v]);
}
// }
if(dfn[u]==low[u]){
colorcnt++;
while(s.top()!=u){
int temp=s.top();
s.pop();
ins[temp]=false;
color[temp]=colorcnt;
colornum[colorcnt]++;
}
s.pop();
color[u]=colorcnt;
colornum[colorcnt]++;
ins[u]=false;
}
}
int main()
{
int n,x;
cin>>n;
for(int i=1;i<=n;i++){
cin>>x;
next[i]=x;
// g[i].push_back(x);
// g[y].push_back(x);
}
for(int i=1;i<=n;i++){
if(dfn[i]==0){
tarjan(i);
}
}
for(int i=1;i<=n;i++){
if(colornum[color[i]]>1){
ans[i]=colornum[color[i]];
}
// else{
// findans(i);
// }
}
// for(int i=1;i<=n;i++){
// cout<<ans[i]<<endl;
// }
for(int i=1;i<=n;i++){
if(ans[i]==0){
findans(i);
}
}
for(int i=1;i<=n;i++){
cout<<ans[i]<<endl;
}
return 0;
}