洛谷P2921 [USACO08DEC]在农场万圣节——非递归的深搜,时间戳,环长,最短路

题目: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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值