Loading

割点与桥

基本概念

  1. 割点:在无向图中,如果删去某个点\(v\)会使得图中的极大连通分量个数增加,则称点\(v\)为割点。

  2. 桥(割边):在无向图中,如果删去某条无向边\((u, v)\)会使得图中的极大连通分量个数增加,则称边\((u, v)\)是割边。

算法思想

割点

若一个顶点\(u\)是割点,当且仅当它满足以下条件之一:

  1. \(u\)不是根结点,且其存在一个子结点\(v\),使得\(low_{v}\) \(\geq dfn_{u}\)
    ,说明\(v\)无法不通过\(u\)到达其祖先。证明:如果\(v\)无法不通过\(u\)到达其祖先,说明\(v\)仅与\(u\)\(u\)的子树连边,割掉\(u\)\(v\)一定会形成一个新的强连通分量。

  2. \(u\)是根结点,且\(u\)有两个或以上的子树。证明:如果割掉\(u\)后,\(u\)的多个子树会无法连通,即形成新的强连通分量。

如果某个顶点\(u\)有一个子结点\(v\),其\(low\)值小于\(u\)的时间戳,说明\(v\)可以不经过\(u\)到达其祖先,所以\(u\)不是割点。

值得注意的是,割点算法的tarjan函数并不能正确地求出图中连通分量的个数、大小和其包含的顶点。因此,在有需要的情况下(如这道题),我们可以写出两个tarjan函数用于不同的用途,或是用特殊的方法更新连通分量的个数,具体见笔者的这篇博文

注意到删除图中的一个割点\(u\),对于每一个\(u\)的子结点\(v\),如果\(low_{v} \geq dfn_{u}\),说明\(v\)只与\(u\)\(u\)的子树连边,所以删去\(u\)后,\(v\)无法与\(u\)的祖先连通,即\(v\)会形成新的强连通分量。

因此,我们可以猜想:如果点\(v\)与点\(u\)是父子关系,且\(low_{v} \geq dfn_{u}\),删去\(u\)后增加的连通分量个数是否等于满足条件的\(v\)的个数?

很显然,如果\(u\)是其所在搜索树的根结点,且其子树个数为\(n\),删去\(u\)后增加的连通分量个数应该是\(n - 1\)。因为\(u\)的子树原本同属于一个连通分量,割去\(u\),相当于把\(n - 1\)棵子树剥离出原来的树\(T\),再把\(T\)的根结点\(u\)删除。因此,增加的连通分量个数应该是剥离出的子树个数,即\(n - 1\)

对于一个点\(u\),其\(cut\)值有以下两种情况:

  1. \(u\)是其所在搜索树的根结点,若\(u\)的子树个数为\(n\),则\(cut_{u} = n - 1\)

  2. \(u\)不是其所在搜索树的根结点,若\(u\)的子树个数为\(n\),则\(cut_{u} = n\)

割边

如果有一对父子顶点\((u, v)\),使得\(low_{v}\) \(>\) \(dfn_{u}\),则边\((u, v)\)是割边。

证明:回顾\(low_{u}\)的定义——不经过\(u\)的父结点所能达到的最小的时间戳。我们将\((u, v)\)删除,相当于求解\(low_{v}\)的值。如果\(low_{v} > dfn_{u}\),说明\(v\)无法到达\(u\)\(u\)的祖先,即删除\((u, v)\)后,\(v\)会形成新的强连通分量。

值得注意的是,如果图\(G\)中存在重边,tarjan函数是不能记录上一个结点的编号以避免往回走的。我们应该记录上一条边的编号,设当前边编号为i,上一条边编号为from,如果from ^ 1 == i,说明边i不可以走,要continue。使用这样的方法判断有一个前提:第一条边的编号从02开始。

模板

割点

例题

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

const int maxn = 2 * 1e4 + 5;
const int maxm = 2e5 + 5;

struct node {
	int to, nxt;
}edge[maxm];

int n, m, cntn;
int cnt_node, cnt_edge, cnt_scc;
int head[maxn], low[maxn], dfn[maxn], color[maxn];
bool in_stack[maxn], cut[maxn];
stack<int> s;

inline void add_edge(int u, int v) {
	cnt_edge++;
	edge[cnt_edge].to = v;
	edge[cnt_edge].nxt = head[u];
	head[u] = cnt_edge;
}

void tarjan(int u, int fa) {
	int child = 0;
	cnt_node++;
	dfn[u] = cnt_node;
	low[u] = cnt_node;
	s.push(u);
	in_stack[u] = true;
	for (int i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to;
		if (!dfn[v]) {
			child++;	//发现u的新子结点 
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			//如果u点满足割点条件且不是根结点 
			if (low[v] >= dfn[u] && u != fa && !cut[u]) {
				cut[u] = true;
				cntn++;
			}
		} else if (in_stack[v]) {
			low[u] = min(low[u], dfn[v]);
		}
	}
	//判断根结点是否是割点 
	if (child >= 2 && u == fa && !cut[u]) {
		cut[u] = true;
		cntn++;
	}
}

int main() {
	int u, v;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d%d", &u, &v);
		add_edge(u, v);
		add_edge(v, u);
	}
	for (int i = 1; i <= n; i++) {
		if (!color[i]) {
			tarjan(i, i);	//发现新的搜索树(连通分量) 
		}
	}
	printf("%d\n", cntn);
	for (int i = 1; i <= n; i++) {
		if (cut[i]) {
			printf("%d ", i);
		}
	}
	puts("");
	return 0;
}

割边

例题(无重边)

参考代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;

const int maxn = 1e5 + 5;
const int maxm = 5e5 + 5;

int n, m;
int cnt_node, cnt_scc;
int low[maxn], dfn[maxn], color[maxn];
bool in_stack[maxn];
vector<int> g[maxn];
vector<pair<int, int> > bridge;
stack<int> s;

void init() {
	memset(low, 0, (n + 1) * sizeof(int));
	memset(dfn, 0, (n + 1) * sizeof(int));
	memset(color, 0, (n + 1) * sizeof(int));
	memset(in_stack, false, (n + 1) * sizeof(bool));
	for (int i = 1; i <= n; i++) {
		while (!g[i].empty()) {
			g[i].pop_back();
		}
	}
	while (!bridge.empty()) {
		bridge.pop_back();
	}
	cnt_node = cnt_scc = 0;
}

void tarjan(int u, int fa) {
	cnt_node++;
	dfn[u] = cnt_node;
	low[u] = cnt_node;
	s.push(u);
	in_stack[u] = true;
	for (int i = 0; i < g[u].size(); i++) {
		int v = g[u][i];
		if (!dfn[v]) {
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u]) {
				int f = min(u, v);
				int s = max(u, v);
				bridge.push_back(make_pair(f, s));
			}
		} else if (in_stack[v] && v != fa) {
			low[u] = min(low[u], dfn[v]);
		}
	}
}

int main() {
	int u, v;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	for (int i = 1; i <= n; i++) {
		if (!color[i]) {
			tarjan(i, 0);
		}
	}
	sort(bridge.begin(), bridge.end());
	for (int i = 0; i < bridge.size(); i++) {
		printf("%d %d\n", bridge[i].first, bridge[i].second);
	}
	return 0;
}

例题:\(hdu4738\)有重边

参考代码如下:

#include <cstdio>
#include <cstring>
#include <stack>
#include <algorithm>
using namespace std;

const int maxn = 1e3 + 5;
const int maxm = 1e6 + 5;
const int inf = 0x3f3f3f3f;

struct node
{
	int to, nxt, w;
}edge[maxm];

int n, m, ans;
int cnt_ver, cnt_edge, cnt_scc;
int head[maxn], dfn[maxn], low[maxn];
bool in_stack[maxn], bridge[maxn];
stack<int> s;

void init()
{
	memset(head, 0, sizeof(head));
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	memset(in_stack, false, sizeof(in_stack));
	memset(bridge, false, sizeof(bridge));
	while (!s.empty())
		s.pop();
	ans = inf;
	cnt_ver = cnt_scc = 0;
	cnt_edge = 1;
}

void add_edge(int u, int v, int w)
{
	cnt_edge++;
	edge[cnt_edge].to = v;
	edge[cnt_edge].w = w;
	edge[cnt_edge].nxt = head[u];
	head[u] = cnt_edge;
}

void tarjan(int u, int from)
{
	cnt_ver++;
	dfn[u] = cnt_ver;
	low[u] = cnt_ver;
	s.push(u);
	in_stack[u] = true;
	for (int i = head[u]; i; i = edge[i].nxt)
	{
		int v = edge[i].to;
		if (!dfn[v])
		{
			tarjan(v, i);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u])
			{
				bridge[i] = bridge[i ^ 1] = true;
				ans = min(ans, edge[i].w);
			}
		}
		else if (i != (from ^ 1))
			low[u] = min(low[u], dfn[v]);
	}
}

int main()
{
	int u, v, w;
	while (scanf("%d%d", &n, &m) != EOF)
	{
		if (!n && !m)
			break;
		init();
		for (int i = 1; i <= m; i++)
		{
			scanf("%d%d%d", &u, &v, &w);
			add_edge(u, v, w);
			add_edge(v, u, w);
		}
		for (int i = 1; i <= n; i++)
		{
			if (!dfn[i])
			{
				cnt_scc++;
				tarjan(i, -1);
			}
		}
		if (cnt_scc > 1)
			printf("%d\n", 0);
		else if (!ans)
			printf("%d\n", 1);
		else if (ans == inf)
			printf("%d\n", -1);
		else
			printf("%d\n", ans);
	}
	return 0;
}
posted @ 2021-07-24 23:14  kymru  阅读(223)  评论(0编辑  收藏  举报