https://www.luogu.org/problemnew/show/P2921
C++版本一
朴素
1、颜色 (color)(color) : 此节点第一次被访问时,这条访问他的路径是由那个节点发出的(起点)。
2、时间戳 (dfn)(dfn) :此节点第一次被访问时,他到发出这条路径的起点的距离(发出节点的 dfn = 0dfn=0,第二个被访问的节点的 dfn = 1dfn=1,第三个 dfn = 2dfn=2 ......)
有了这两个属性,我们就可以计算环的大小,方法如下:
1、从某一节点发出路径
2、走到某个节点上(包括起点),如果这个节点没有被染色,那么染成自己的颜色,并标记上 dfndfn
3、走到某个节点上,如果这个节点已经被染成了自己的颜色,那么环的大小就出来了:当前时间 (cnt)(cnt) -− 此节点 dfndfn
到了这一步,实际上已经解决了另一个更简单的问题:NOIP2015 信息传递。 接下来就是本题特色了
1、环的大小 (minc)(minc) :每条路径最终都会进入环中,或者起点本身就在环中,我们记录下这个环的大小为之后服务
2、入环时间戳 (sucdfn)(sucdfn) :这条路径什么时候会进入环中,同样是为之后服务的一个属性
首先讲解一下如果得到这两个属性:
1、在上一节中我们已经讲了如何初步获取环的大小,入环时间戳只要记录为那个交点的时间戳即可
2、如果走到了之前走过的节点,那么新的路径必然进入之前路径的环中,直接把这个环的大小要过来就行了。入环时间戳则分两种情况:
i. 如果这个节点不在环中,“原路径的入环时间戳 -− 原路径中此节点的时间戳 + 新路径当前时间” 即为新路径的入环时间戳;
ii. 而如果这个节点在环中,直接就是新路径当前时间。
iii. 判断方法则是 “原路径的入环时间戳 -− 原路径中此节点的时间戳” 是否大于 00,综合起来就是:“max(max(原路径的入环时间戳 -− 原路径中此节点的时间戳, \;0),0) + 新路径当前时间”
1、第一节中的发现环的大小之后,答案就是“当前时间”
2、第二节中与之间走过的节点相遇并记录完信息后,答案是 “入环时间戳 + 环的大小”
至此本题已经完美解决,且没有用到任何算法。贴代码:
/*
*@Author: STZG
*@Language: C++
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
C++版本二
强连通分量SCC
参考文章:https://blog.csdn.net/weixin_43272781/article/details/88557457
#include
using namespace std;
const int Maxn=100005;
int next[Maxn];
int ans[Maxn];
int head[Maxn],cnt;
struct road
{
int to,next;
}e[Maxn*2];
void add(int a,int b)
{
cnt++;
e[cnt].to=b;
e[cnt].next=head[a];
head[a]=cnt;
}
int sum,color[Maxn],low[Maxn],ins[Maxn],tim[Maxn],sta[Maxn],top=1,col;
int Lemon[Maxn];
void Tarjan(int x)
{
sum++;
tim[x]=low[x]=sum;
sta[top]=x;
top++;
ins[x]=1;
for(int i=head[x];i!=0;i=e[i].next)
{
if(ins[e[i].to]==0)
{
Tarjan(e[i].to);
low[x]=min(low[x],low[e[i].to]);
}
else if(ins[e[i].to]==1)
low[x]=min(low[x],tim[e[i].to]);
}
if(tim[x]==low[x])
{
col++;
do
{
top--;
color[sta[top]]=col;
ins[sta[top]]=-1;
}while(sta[top]!=x);
}
return ;
}
void search(int root,int x,int step)
{
if(ans[x]!=0) {
ans[root]=ans[x]+step;
return ;
}
else search(root,next[x],step+1);
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&next[i]);
add(i,next[i]);
if(next[i]==i) ans[i]=1;//注意特判环为1的情况。
}
for(int i=1;i<=n;i++)
if(ins[i]==0) Tarjan(i);
for(int i=1;i<=n;i++)
Lemon[color[i]]++;//记录环的大小
for(int i=1;i<=n;i++)
if(Lemon[color[i]]!=1) ans[i]=Lemon[color[i]];//处理在环内的点
for(int i=1;i<=n;i++)
if(ans[i]==0) search(i,next[i],1);//处理在环外的点。
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}
C++版本三
#include
#include
using namespace std;
const int N=1e5+5;
int n,to[N],rd[N],w[N],dep[N],mk[N];char vis[N],ins[N];
int dfs(int x){
int t=to[x];vis[x]=ins[x]=1;
if(!vis[t]){dep[t]=dep[x]+1;w[x]=dfs(t);w[x]+=(mk[t]?(mk[x]=(mk[x]!=2?1:0),0):1);}
else if(ins[t])w[x]=dep[x]-dep[t]+1,mk[x]=1,mk[t]=(x==t?0:2);
else w[x]=w[t]+1;
ins[x]=0;return w[x];
}
int main(){
ios::sync_with_stdio(false);cin>>n;int x;
for(int i=1;i<=n;++i)cin>>to[i],++rd[to[i]];
for(int i=1;i<=n;++i)if(!rd[i])w[i]=1,dfs(i);
for(int i=1;i<=n;++i)if(!vis[i])dfs(i);//totally a cycle
for(int i=1;i<=n;++i)cout<
C++版本四
题解:
拓扑排序删链 → 对环上的点计算答案 → dfs计算其他点的答案。
环上的点答案都一样,可以一遍dfs跑出来;
其他点的答案在dfs返回的时候+1即可。
#include
#define maxn 100010
int next[maxn], in[maxn], ans[maxn];
bool vis[maxn];
void del(int cur) {
vis[cur] = true;
in[next[cur]]--;
if(!in[next[cur]]) del(next[cur]);
}
int calccircle(int cur, int depth) {
ans[cur] = depth;
if(ans[next[cur]]) return depth;
else return ans[cur] = calccircle(next[cur], depth + 1);
}
int calc(int cur) {
if(ans[cur]) return ans[cur];
else return ans[cur] = calc(next[cur]) + 1;
}
int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", next + i), in[next[i]]++;
for(int i = 1; i <= n; i++) if(!in[i] && !vis[i]) del(i);
for(int i = 1; i <= n; i++) if(in[i] && !ans[i]) calccircle(i, 1);
for(int i = 1; i <= n; i++) if(!in[i] && !ans[i]) calc(i);
for(int i = 1; i <= n; i++) printf("%d\n", ans[i]);
}