割点&割边 点双&边双

posted on 2023-07-29 14:11:34 | under 笔记 | source

upd on 2026:远古博客,建议查看新写的“连通性相关问题”。

基础概念

定义

割点:在无向连通图中,若去掉某点(即去掉所有与之相连的边)后,该图不连通,则称这个点是割点。

割边:在无向连通图中,若去掉某边后,该图不连通,则称这条边是割边,也称桥。

点双连通图:不存在割点的图,两点一线除外。

边双连通图:不存在割边的图,两点一线除外。

点双:极大的点双连通图。

边双:极大的边双连通图。

性质

1.点双(除两点一线型)中任意两点间至少存在两条点不同的路径,即任意两点被包含在至少 1 个简单环中。

证明:若两点只存在一条点不同的路径,则路径上度大于 1 的点就是割点,与定义相违背。

2.割点连接着至少 2 个点双,非割点的点至多存在于 1 个点双中。这是显而易见的。

3.割边(除两点一线型)中任意两点间至少存在两条边不相同的路径,即任意一边都被包含于至少 1 个简单环中。

证明:同 1 的证法。否则点双会出现割边。

4.点双连通不具传递性(因为点会重合),边双连通具有传递性。

割点与割边的求法

这里要请出 Tarjan 大法了,在这之前,补充一些概念:

1.dfs 树:即 dfs 遍历得到的树,注意是有向的。

2.树边:dfs 树上由祖先节点指向儿子节点的边。

3.非树边:dfs 树上由儿子节点指向祖先节点的边。

割点

定义数组:

1.dfn[]:记录节点的 dfs 序。

2.low[]:记录节点经过至多 1 条非树边和若干条树边后所到点的 dfn 最小值。

类似求解强连通分量,维护上述数组。

关键是判定:令 v 为节点 u 的子节点,若 \(low[v]≥dfn[u]\),则 u 为割点。

如何理解?回顾定义,显然 \(dfn[v]>low[u]\);如果 v 可以通过绕远路的方式到达 \(u\),则 \(low[v]≤dfn[u]\)

若满足判定条件,则说明 v 无法绕远路到达除 u 外已遍历的点,放在原图这代表 v 必须经过 u 才能到达这些点。于是去掉 u 后,原图不再连通,u 便是割点。

代码如下。

注意根节点的特判(若树上根节点的出边大于 1,则根节点为割点)。

void Tarjan(int k)
{
	dfn[k]=low[k]=++df;
	int ch=0;
	for(int i=0; i<e[k].size();i++)
	{		
		int v=e[k][i];
		if(dfn[v]==0)
		{	
			Tarjan(v);
			low[k]=min(low[k],low[v]);
			if(low[v]>=dfn[k]&&k!=root) //根节点特判
			{
				pt[k]=1; //pt数组标记割点
			}
			ch++;
		}
		else
		{
			low[k]=min(low[k],dfn[v]);
		}
	}
	if(k==root&&ch>1) //当根节点是割点时,防止少算根节点
	{
		pt[k]=1;
	}
}

割边

除判定外与割点求法一致。若 \(low[v]>dfn[u]\),则边 (u,v) 为割边。

为啥没有等于呢?关键是边双定义为 任意两点间至少存在两条边不同的路径,一点可连接多条边,看图有答案:

如上图,1、3 点之间的两条路径点相同,边不同。

点双

只需遍历时把所有点压入栈,遇到割点就不断弹出直到遇见割点,正确性的证明类比强连通分量。

需要注意的是,割点可能被多个点双包含,因此不能弹出但要记录。推荐写法:不断弹出到点 v,最后记录 u 即可。

代码如下。

void Tarjan(int k)
{
	dfn[k]=low[k]=++df;
	st.push(k);
	int cnt=0;
	for(int i=0; i<e[k].size();i++)
	{
		int v=e[k][i];
		if(dfn[v]==0)
		{
			cnt++;
			Tarjan(v);
			low[k]=min(low[k],low[v]);
			if(low[v]>=dfn[k])
			{
				ans++;
				int p;
				do
				{
					p=st.top();
					st.pop();
					answer[ans].pb(p); //answer数组记录点双
				}while(p^v);
				answer[ans].pb(k);
			}
		}
		else
		{
			low[k]=min(low[k],dfn[v]);
		}
	}
	if(cnt==0&&k==root) //判断孤立点
	{
		ans++;
		answer[ans].pb(k);
		return ;
	}
}

边双

可以求出每条割边,对其标记后再做 dfs 染色即可,但是比较复杂。

仔细想想,Tarjan 已经把无向图化为有向图,边不再具有不确定性。而边双可看作是环,环也是强连通分量,考虑仿照强连通分量的做法对图进行缩点。

区别在于有向图与无向图,无需判断子节点是否已在强连通分量中,vis 就没有存在的意义了。

解释上句:无向图的 dfs 树中不存在横叉边,所以不可能出现 v 在其他边双的情况。

代码如下:

void Tarjan(int fa,int k)
{
	dfn[k]=low[k]=++df;
	st.push(k);
	int fl=0;
	for(int i=0; i<e[k].size();i++)
	{
		int v=e[k][i];
		if(fa==v&&fl==0) //判定重边
		{
			fl=1;
			continue;
		}
		if(!dfn[v]) 
		{
			Tarjan(k,v);
		}
		low[k]=min(low[k],low[v]);
	}
	if(low[k]==dfn[k])
	{
		cnt++;
		int p;
		do
		{
			p=st.top();
			st.pop();
			ans[cnt]++; //记录大小
			ans2[cnt].pb(p); //记录每个点
		}while(p^k);
	}
}

具体应用

题目多与点双、边双有关。注意它们的区别,对题目进行点双缩点或边双缩点(模板)。当然,最重要的是结合其它知识。

posted @ 2026-01-14 17:57  Zwi  阅读(0)  评论(0)    收藏  举报