题目:https://www.luogu.org/problemnew/show/P2921
注意到点最多有100000个,肯定不能用邻接矩阵,也不宜用前向星,因为每个结点的出度都是1,因此用数组a[]存图,a[i]表示i的后继结点。
为叙述方便,给出例子。
1->2->3->4->5->6->7->4
9->8->2
10->5
引入染色块color[],时间戳dfn[],环长mincycle[],进环时间戳incycle[]。
从1开始深搜,则结点1,2,3,4,5,6,7的时间戳分别为1,2,3,4,5,6,7。结点7指向结点4,可以判断出结点4,5,6,7构成最小环。特别要强调的技巧是引入染色,因为是从结点1开始深搜,所以结点1——7都染成1,即color[1..7]=1,并称这条路径为线路1。相应的线路1的环长mincycle[1]=dfn[7]-dfn[4]+1。进环时间戳incycle[1]=dfn[4]=4。
从结点8开始深搜,结点8的后继结点是结点2,而结点2已经被染成1,结点2不在环内。从结点8出发的线路8,它的进环时间戳应该是4,环长是线路1的最小环长。
从结点10开始深搜,结点10的后继结点是5,结点5已经被染成1,结点5在环内。从结点10出发的线路10的进环时间戳是2,环长是线路1的环长。
AC代码注释比较详细:
#include<cstdio>
#include<iostream>
#define MaxSize 100005
using namespace std;
int n,a[MaxSize];
int clr[MaxSize],dfn[MaxSize],incycle[MaxSize],mincycle[MaxSize];
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
if(clr[i]==0){/*i还不在任何线路中*/
int cns=0;
for(int j=i; ;j=a[j]){
if(clr[j]==0){
clr[j]=i;/*j属于编号为i的线路*/
dfn[j]=++cns;
}
else if(clr[j]==i){/*线路i全程都是新的*/
incycle[i]=dfn[j];/*进环时间戳*/
mincycle[i]=cns-dfn[j]+1;/*当前连通块的环长*/
cout<<incycle[i]+mincycle[i]-1<<endl;
break;
}
else if(clr[j]!=i){/*线路i将在某处与一条已有的线路pre_i连接*/
int pre_i=clr[j];/*j所在的线路编号记为pre_i*/
if(dfn[j]< incycle[ pre_i]){/*某处不在环上*/
incycle[i]=cns+ (incycle[ pre_i ]-dfn[j] +1 );
mincycle[i]=mincycle[ pre_i ];
cout<<incycle[i] - 1 +mincycle[i]<<endl;
}
else{/*某处就在环上*/
incycle[i]=cns+1;
mincycle[i]=mincycle[ pre_i ];
cout<<mincycle[i] +cns <<endl;
}
break;
}
}
}
else{/*i已经在某个线路中*/
int pre_i=clr[i];/*i所在的线路编号记为pre_i*/
if(dfn[i]<incycle[pre_i]) /*某处不在环上*/
cout<<incycle[pre_i] - dfn[i] +mincycle[pre_i]<<endl;
else /*某处就在环上*/
cout<<mincycle[pre_i]<<endl;
}
}
return 0;
}