无向图的割点与桥

给定无向连通图:

  • 对于其中一点 \(u\),若从图中删掉 \(u\) 和所有与 \(u\) 相连的边后,原图分裂成成 \(2\) 个或以上不相连的子图,则称 \(u\) 为原图的割点(或割顶)
  • 对于其中一边 \(e\),若从图中删掉 \(e\) 后,原图分裂成 \(2\) 个或以上不相连的子图,则称 \(e\) 为原图的桥(或割边)
  • 一般无向图(不保证连通)的割点与桥就是它各个连通块的割点与桥。

\(\rm Tarjan\) 算法可以在 \(\operatorname{O}(n)\) 内求出所有割点与桥。

跟求 \(\rm SCC\) 类似,我们也需要用到 \(dfn\)\(low\) 数组,其意义和求 \(\rm SCC\) 时的 \(dfn,low\) 数组类似。

1. 割点

\(u\) 不是搜索树的 \(root\),则 \(u\) 是割点当且仅当树上至少有 \(u\)\(1\) 个子节点 \(v\) 满足:

\[dfn(u)\le low(v) \]

\(u\)\(root\),则 \(u\) 是割点当且仅当 \(u\) 至少有 \(2\) 个子节点满足上述条件。

\(dfn(u)\le low(v)\) 说明从 \(subtree(v)\) 出发,若不经过 \(u\),则无法到达比 \(u\)\(dfn\) 更小的节点,那么我们把 \(u\) 删掉,原图就被分成了 \(subtree(v)\) 和剩下的节点至少 \(2\) 个子图。由于割点允许 \(dfn(u)=low(v)\),所以 \(dfn(fa)\) 可以用来更新 \(low(u)\)。。

P3388 【模板】割点(割顶)

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 2e4 + 5;
const int MAXM = 1e5 + 5;

int cnt, Time, rt, tot;
int head[MAXN], dfn[MAXN], low[MAXN];
bool cut[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

void tarjan(int u)
{
	dfn[u] = low[u] = ++Time; //初始化dfn和low
	int flag = 0;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v]) //low值的更新和求SCC时类似
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
			if (dfn[u] <= low[v])
			{
				flag++;
				if (u != rt || flag > 1) //满足x不是根节点,或者x是根节点且有至少2个满足要求的子节点
				{
					if (!cut[u]) //防止重复统计
					{
						tot++;
					}
					cut[u] = true;
				}
			}
		}
		else
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i])
		{
			tarjan(rt = i); //搜索树的根是i
		}
	}
	printf("%d\n", tot);
	for (int i = 1; i <= n; i++)
	{
		if (cut[i])
		{
			printf("%d ", i);
		}
	}
	return 0;
}

2. 桥

搜索树上 \(u\) 的子节点是 \(v\),则边 \(<u,v>\) 是桥,当且仅当:

\[dfn(u)<low(v) \]

\(dfn(u)<low(v)\) 说明从 \(subtree(v)\) 出发,若不经过 \(<u,v>\),则无法到达 比 \(u\)\(dfn\) 更小的节点,那么我们把 \(<u,v>\) 删掉,原图就被分成了 \(subtree(v)\) 和剩下的节点至少 \(2\) 个子图。

值得注意的是:因为是无向边,所以从 \(u\) 出发总能回到它的 \(fa\)。根据 \(low\) 的定义,\(<u,fa>\) 是树边且 \(fa\notin subtree(u)\),所以 \(dfn(fa)\) 不能用来更新 \(low(u)\)!!!

但是你以为这样就完了吗?

毒瘤数据会出现重边!!!

对于重边,只有一条算树边,所以有重边时,\(dfn(fa)\) 又能用来更新 \(low(u)\) 了。

机房某 dalao:你\(*\)炸了

处理方法:将读入的边成对储存在 \(e(2)\)​ 和 \(e(3)\)​,\(e(4)\)​ 和
\(e(5)\dots e(2n)\)​ 和 \(e(2n+1)\)​ 里。

观察:

\(2\,\operatorname{xor}\,1=3\)​​

\(4\,\operatorname{xor}\,1=5\)​​

\(\cdots\cdots\)

\(2n\,\operatorname{xor}\,1=2n+1\)​​

若通过 \(e(i)\)​ 进入 \(u\)​,则 \(e(i)\)​ 和 \(e(i\operatorname{xor}1)\)​ 本质上是同一条无向边,故除了 \(e(i\operatorname{xor}1)\)​ 之外的边都能用来更新 \(low(u)\)​。

P1656 炸铁路

另外吐槽一句,这题作为桥的模板本来应该评绿的,结果因为数据范围较小可以用暴力过直接给评黄了……

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 155;
const int MAXM = 5005;

int cnt = 1, Time, tot; //注意这里!因为边是存在(2,3),(4,5)……内的,所以cnt要初始化为1!
int head[MAXN], dfn[MAXN], low[MAXN];

struct edge
{
	int to, nxt;
}e[MAXM << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

struct ans
{
	int from, to;
	bool operator <(const ans &x)const
	{
		if (x.from != from)
		{
			return x.from > from;
		}
		return x.to > to;
	}
}a[MAXM << 1];

void add_ans(int u, int v)
{
	a[++tot] = ans{min(u, v), max(u, v)};
}

void tarjan(int u, int in_edge)
{
	dfn[u] = low[u] = ++Time;
	for (int i = head[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]) //是桥,把答案存进去
			{
				add_ans(u, v);
			}
		}
		else if (i != (in_edge ^ 1)) //不是同一条无向边
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i])
		{
			tarjan(i, 0);
		}
	}
	sort(a + 1, a + tot + 1); //按照题目要求输出
	for (int i = 1; i <= tot; i++)
	{
		printf("%d %d\n", a[i].from, a[i].to);
	}
	return 0;
}

一个好玩的性质

除了图中只有两点一线的情况外,桥的两个端点一定都是割点、

posted @ 2021-08-07 18:12  mango09  阅读(171)  评论(0编辑  收藏  举报
-->