割点割边 + 点双边双 学习笔记

本文深入探讨图论中的割点、割边概念,以及如何通过Tarjan算法求解割点和割边。同时讲解了一点双连通分量和边双连通分量的定义,给出了相关模板代码,并提供了实际问题的应用场景,如构建点双连通分量和使图成为边双的方法。
摘要由CSDN通过智能技术生成

割点

定义

在一个无向图(强连通分量仅针对有向图)中,若去掉一个点 u,该图有两点不连通,则称点 u 为割点。

点连通度:该连通(且无向)图的割点数量。

求割点

对于点 u:

  1. u 为一个根节点(把图看成树):因为他是根节点,所以它的入度为 0,那么只要它有两个及以上的孩子,那么它就是割点(去掉它了会有两个以上的连通块), c h i l > 1 chil>1 chil>1

  2. u 为子节点:若它的子节点 v 不能够不通过 u 连到 v 的边到 u 的祖先,则只有加上从 u 到 v 的边才能让 v 到 u 的祖先,那么此时 u 就是割点(去掉它之后它的子节点无法和它的祖先相连通), l o w v > = d f n u low_v>=dfn_u lowv>=dfnu

其他跟求强连通分量的板子相差不大。

P3388 割点板子

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	int tot = 0;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tot++;
			tarjan (v);
			low[u] = min (low[u], low[v]);
			if ((u == root and tot > 1) or (u != root and dfn[u] <= low[v])) vis[u] = 1;
		}
		else low[u] = min (low[u], dfn[v]);
	}
}

具体根据题目排判断顺序:可以先判 l o w v > = d f n u low_v>=dfn_u lowv>=dfnu,然后处理某些东西,然后再用 u ! = r o o t ∣ ∣ c h i l > 1 u!=root||chil>1 u!=rootchil>1 判断割点。(8.10 比赛的T4)

割边

更割点差不多,就是从点变成了边,定义(例如边联通分量)都相似。

求割边

只要 v 不能不通过 ( u , v ) (u,v) (u,v) 到达 u 或 u 的祖先,那么 ( u , v ) (u,v) (u,v) 就是割边。

话说割点和边真就一个符号的区别。。

P1656 割边板子

void tarjan (int u, int li)
{
	dfn[u] = low[u] = ++tmp;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan (v, i);
			low[u] = min (low[u], low[v]);
			if (dfn[u] < low[v]) ans[++cntn] = (abc){min (u, v), max (u, v)};
		}
		else if (i != find (li)) low[u] = min (low[u], dfn[v]);
	}
}

点双

定义

在一无向图中,若该图没有割点,则称它为一点双连通分量。

运用:

  1. 在一无向图中找出若干点双(8.13 T4 点双);

  2. 计算还要添加多少节点才能使得原图变成点双(这个不常见吧,至少到现在也没见着 一般边双有这操作)。

求点双

一无向图中,我们是按照割点去分点双的(一割点可能被包含在多个点双中)。

先 Tarjan 求出每一个割点(记得每访问一个节点要压入栈中),

然后每找到一个割点,我们就立马把节点从栈中倒出来

但是要注意,割点 u 不要倒出来,因为后面它可能还会在另外一个点双中被再次访问到。

T103492 点双模板

void tarjan (int u, int f)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	co[u] = 1;
	if (u == rt and !hd[u])
	{
		cout << u << endl;
		return;
	}
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan (v, u);
			low[u] = min (low[u], low[v]);
			if (dfn[u] <= low[v])
			{
				col++;
				while (st[top] != u)
				{
					cout << st[top] << " ";
					co[st[top]] = 0;
					if (st[top] == v) {top--;break;}
					top--;
				}
				cout << u << endl;
			}
		}
		else if (v != f) low[u] = min (low[u], dfn[v]);
	}
}

边双

P2860 Redundant Paths G

这道题可以当作是边双模板吧。。。

定义

嗯,定义和点双类似的。

只要一无向图中没有出现割边,那么它就是边双连通分量。

不同的是,割点可能存在于多个点双中,但割边是不可能存在于任何一个边双中的。(这个很好理解的吧。)

求边双

运用:主要就一个,询问给一无向图添加多少条边才能使得它是边双。

边双是由一条条割边分开来的,我们要判断当前图是否是边双,只需要看它成不成环。

如果能从点 u 找到一条环使得还能从它走回 u,证明点 u 存在于一个边双中(可根据桥的定义理解)。

然后我们进行缩点操作(不过这里的比算法笔记里的简单多了)。

我们只需要在遍历每一条边的时候用割点的思想(是否在同一边双中)判是否需要建这条边,

如果需要,我们只要 d u ( c o ( u i ) ) ← d u ( c o ( u i ) ) + 1 du (co(u_i)) \gets du(co(u_i))+1 du(co(ui))du(co(ui))+1 以及 d u ( c o ( v i ) ) ← d u ( c o ( v i ) ) + 1 du (co(v_i)) \gets du(co(v_i))+1 du(co(vi))du(co(vi))+1——两个边双的入、出度加一。

如果不需要直接 continue; 处理下一条边即可。

最后,如果有节点(边双)度数为 1,我们就统计下来——有度数为 1 的点就无法成环。

最后我们让度数为 1 的点两两相连,如果总数为奇数,就给剩下那个点单独一条边就好了—— a n s + 1 > > 1 ans+1>>1 ans+1>>1

代码

Tarjan (记得双向边建成两条单向边 后面要判断是否已经处理过了。)

inline void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	for (int i = hd[u]; i; i = e[i].nxt)
	if (!vis[i])
	{
		int v = e[i].to;
		vis[i] = vis[i ^ 1] = 1;
		if (!dfn[v])
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else low[u] = min (low[u], dfn[v]);
	}
	if (dfn[u] == low[u])//是环就直接倒出来
	{
		co[u] = ++col;
		while (st[top] != u)
		{
			co[st[top]] = col;
			top--;
		}
		top--;
	}
}

主函数部分

int main ()
{
	memset(hd,0,sizeof(hd));
	memset(dfn,0,sizeof(hd));
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		scanf ("%d %d", &u[i], &v[i]);
		add (u[i], v[i]), add (v[i], u[i]);	
	}
	for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan (i);
	int ans = 0;
	for (int i = 1; i <= m; i++) if (co[u[i]] != co[v[i]]) du[co[u[i]]]++, du[co[v[i]]]++;
	for (int i = 1; i <= col; i++) if (du[i] == 1) ans++;
	printf ("%d\n", ans + 1 >> 1);	
	return 0;
}

—— E n d End End——

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值