图论割点和割边学习指南

前置芝士

割点

如果去掉一个点以及与它连接的边,该点原来所在的图被分成两部分(不连通),则称该点为割点。

img

图中2号顶点就是割点

[判定]

(1)如果x不是根节点,当搜索树上存在x的一个子节点y,满足low[y]>=dfn[x],那么x就是割点。

(2)如果x是根节点,当搜索树上存在至少两个子节点y1,y2,满足上述条件,那么x就是割点。

Tarjan算法

时间复杂度:O(N+M)

如何判断割点

暴力解法:依次删除每一个顶点,然后用dfs或者bfs来检查图是否依然连通。如果删除某个顶点后,导致图不再连通,那么刚才删除的顶点就是割点。时间复杂度是O(N(N+M))。显然时间复杂度是无法接受的。

判断砍掉一个点后,连通分量的个数是否增多

(1)任选无向图中的一个点,作为树的根,然后通过dfs遍历全图。

(2)设 dfn[i] 代表节点 i 的dfs序。

注:dfs序指的是在dfs过程中,该节点是第几个被访问到的节点。显而易见的,dfn数组的值随dfs过程单调上升。

img

如上图,假设节点1为根节点,则节点右上角红框内的数字为其dfn。

(3)设 low[i] 代表节点 i 在不直接返回父亲节点的情况下,通过“绕路”,能够访问到的节点里,最小的dfn值。

img

如上图,假设节点1为根节点,则节点右上角蓝框内的数字为其low。

(4)对于根节点,若其有两棵及以上的子树,则根节点为割点。

因为如果去掉根节点,两棵子树将无法互相连通。

(5)对于非根节点u,若存在一子节点v,无法通过“绕路”,访问到u除其自己外的祖先节点,则u为割点。

因为一旦去除u,u的祖先节点将与v互不连通。

此时, low[v]>=dfn[u] 。

那么,问题就转化成了如何求 low[i] 。

假设dfs过程中,当前所在节点为u,有一条边(u,v),则有如下两种情况。

①若节点v尚未访问过,则说明节点v是u的子节点。

子节点能绕路访问的dfn最小点,将对父节点做出贡献,即 low[u]=min(low[u],low[v]) 。

②若节点v已访问过,且v不是父节点,则按照dfs的规律——节点v即为某个祖先节点。故这条边是一条返祖边(又叫回边)。

此时 low[u]=min(low[u],dfn[v]) 。

①该点为根节点时,若子树数量大于一则说明该点为割点(子树数量不等于与该点连接的边数)。

②该点不为根节点时,若存在一个儿子节点的low值大于或等于该点的dfn值时( low[子节点] >= dfn[父节点] ),该点为割点(即子节点,无法通过回边,到达某一部分节点(这些节点的dfn值小于父亲节点))。

割点所割连通分量数

①设 cut[i] 表示割点 i 所割连通分量数。

②对于根节点的割点,显而易见的,它的孩子数就是所割的连通分量数,故 cut[i]=child[i] 。

③对于非根节点的割点,它所割的连通分量数为其满足条件 low[v]>=dfn[u] 的孩子数+1。因为其与父节点的连通,也会因为割点的去除而失去。

求所有割点节点

[problem description]

给出一个 \(n\) 个点,\(m\) 条边的无向图,求图的割点。

[input]

第一行输入两个正整数 \(n,m\)

下面 \(m\) 行每行输入两个正整数 \(x,y\) 表示 \(x\)\(y\) 有一条边。

[output]

第一行输出割点个数。

第二行按照节点编号从小到大输出节点,用空格隔开。

[a.in]

6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6

[a.out]

1 
5

[datas]

\(1\leq n \le 2\times 10^4\)\(1\leq m \le 1 \times 10^5\)

点的编号均大于 \(0\) 小于等于 \(n\)

tarjan图不一定联通

[solved]

const int N = 20010;
vector<int> e[N];
int n, m, res;
bool cnt[N];
int dfn[N], low[N], tot;
void tarjan(int u, int top) {
	dfn[u] = low[u] = ++tot;
	int child = 0;
	for (auto v : e[u]) {
		if (!dfn[v]) {
			tarjan(v, top);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u] && u != top) {//
				cnt[u] = true;
			}
			if (u == top) child++;
		} else {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (child >= 2 && top == u) cnt[u] = true;
}

void solve() {
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	cin >> n >> m;
	for (int i = 1, x, y; i <= m; i++) {
		cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i, i);
	for (int i = 1; i <= n; i++) if (cnt[i])  res++;
	cout << res << endl;
	for (int i = 1; i <= n; i++) {
		if (cnt[i]) cout << i << " ";
	}
	cout << endl;
}
posted @ 2023-10-27 20:48  White_Sheep  阅读(21)  评论(0编辑  收藏  举报