tarjan求割点 / 割边

tarjan求割点 / 割边


在求割点 / 割边前,首先要理解trajan算法中两个重要数组的意义,dfn[]和low[]。
dfn[]为时间戳,表示这个点在dfs中是第几个被访问到的。
low[]表示该节点通过回边(非父子边)可以回溯到的最早节点。

割边

在遍历一个点x所有边的过程中,这条边是割边的充要条件是low[i]>low[x],即点i无法回溯到比x更早的点(如下图所示),那么此时删除这条边一定会让x和i无法连接,即其为割边。
在这里插入图片描述

在这里插入图片描述
而当low[i]<=low[x]时,即i可以回溯到早于x的节点,那么通过上图可以很清晰看出删除x到i这条边连通性不发生改变。
求割边代码如下

ll dfn[maxn],low[maxn],fa[maxn],cnt;
vector<ll>g[maxn];
void tarjan(ll x){
	low[x]=dfn[x]=++cnt;
	for(auto i:g[x]){
		if(!dfn[i]){
			fa[i]=x;//标记i的父节点为x
			tarjan(i);
			low[x]=min(low[x],low[i]);//更新low数组
		}
		else if(i!=fa[x]){//当这个点访问过且不是父节点时更新low数组
			low[x]=min(low[x],dfn[i]);
		}
	}
}

割点

当遍历到一个点x时,这个点为割点的情况有两种:一种是该节点为根节点且子节点数大于等于2(如左图所示),则删掉这个节点后必将导致两个子节点不连通;第二种情况是该节点不为根节点且low[i]>=low[x](如右图所示),即子节点i可回溯到的最早节点不早于x点,那么删去x点一定会导致x的父节点与x的子节点不连通。
在这里插入图片描述

ll dfn[maxn],low[maxn],fa[maxn],cnt;
vector<ll>g[maxn];
set<ll>s;
void tarjan(ll x){
	low[x]=dfn[x]=++cnt;
	ll child=0;//统计x的子节点
	for(auto i:g[x]){
		if(!dfn[i]){
			fa[i]=x;//标记点i的父节点为x
			child++;
			tarjan(i);
			low[x]=min(low[x],low[i])if(low[i]>=low[x]&&fa[x])s.insert(x);
			//无法回溯到早于x的节点则x为割点
		}
		else if(i!=fa[x]){//这里的if其实可以去掉
			low[x]=min(low[x],dfn[i]);
		}
	}
	if(!fa[x]&&child>=2)s.insert(x);
	//x为根节点且有两个子节点则x为割点
}

值得注意的是x可能会在循环中反复被判定为割点,所以这里我把第一种情况的判断移到循环外(大部分题解都是两个if放在循环内)而且还用set容器去重,题目中更常用的应该是用标记数组vis[],这样时间复杂度会降低许多。同时 else if 这一句可以直接写成else,因为回溯到父节点也是可以被判定为割点的,如果能回溯更早而导致x不被判定为割点,这个low数组也是会被更新的,所以直接写成else是没问题的(而且我看到大部分人都直接写的else),但我这里为了和割边的代码统一起来以及为了符合low数组的意义所以还是写的 else if 。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值