浅谈v_DCC缩点

之前有提到过e_DCC缩点,是求出边双连通分量,将每个边双连通分量看作一个点,桥为边,构建新图,v_DCC缩点更复杂一点。

e_DCC中,每个边只会属于一个e_DCC,且桥不会属于任何e_DCC。但是v_DCC就有点不同了,v_DCC中, 割点可能属于多个v_DCC 。OK,那就还是先求割点呗。

求割点,那就tarjan呗,但是有两点要注意
  1、如果一个点孤立(没有边相连),它是割点
  2、如果tarjan过程中发现当前搜索树的根节点满足判定式 d f n [ x ] ≤ l o w [ x ] dfn[x]\leq low[x] dfn[x]low[x] ,它也不一定是割点,它需要有至少两个子节点才行

求点双连通分量就是在求割点基础上加一丢丢操作(有学过有向图强连通分量的tarjan求法的会感觉很相似),多了一个栈,在tarjan求搜索树的过程中,第一次访问到某个点时,将点入栈,当满足割点判定式 d f n [ x ] ≤ l o w [ y ] dfn[x] \leq low[y] dfn[x]low[y] ,就将栈中元素依次弹出,知道弹出的元素式 y y y的时候,终止弹出即可,然后这些被弹出的点就构成了一个v_DCC。但是,不能像求强连通分量一样,用一个数组记录每个点属于哪个强连通分量,因为割点可能属于多个v_DCC,因此改为,用一个向量( v e c t o r vector vector),其中 v e c t o r [ i ] vector[i] vector[i]表示第 i i i个v_DCC中所有的点,这样就可以将割点放到多个v_DCC中。

如果你还想求每一个点(非割点)属于哪个v_DCC,可以tarjan过程中仿照强连通分量的做法,将每一个被弹出的点直接施加标记,或者在求完所有的v_DCC后,遍历所有的v_DCC,然后施加标记(推荐第二种)

就剩下建图了呗,那就建图呗。e_DCC种建图是以桥为边,连接不同的e_DCC,v_DCC中又与他不同了。在v_DCC中,我们现在求得了所有的割点和v_DCC,假设有 p p p个割点, k k k个v_DCC,那么新图中就有这么 p + k p+k p+k个点,然后在每个割点与它所在的v_DCC之间连边(可能属于多个v_DCC),就得到了新图。

// 求割点
#include <bits/stdc++.h>
#define mem(a, b) memset(a, b, sizeof a)
using namespace std;
const int N = 310;
int head[N], nex[N], to[N], cnt;
int new_id[N], id;
int head1[N], nex1[N], to1[N], cnt1;
int dfn[N], low[N], num;
bool cut[N];
int root;
int st[N], top;
int v_DCC[N], dcc;
vector<int > vec[N];
int n, m;
void init() {
    
    
	cnt = 0, num = 0, top = 0, dcc = 0, id = 0;
	mem(head, 0), mem(nex, 0), mem(dfn, 0), mem(low, 0), mem(cut, 0), mem(v_DCC, 0);
	mem(head1, 0), mem(nex1, 0), mem(new_id, 0), cnt1 = 0, id = 0;
	for (int i = 0; i < N; i++)vec[i].clear();
}
void add(int a, int b) {
    
    
	++cnt;
	to[cnt] = b;
	nex[cnt] = head[a];
	head[a] = cnt;
}
void add1(int a, int b) {
    
    
	++cnt1;
	to1[cnt1] = b;
	nex1[cnt1] = head1[a];
	head1[a] = cnt1;
}
void tarjan(int x) {
    
    
	dfn[x] = low[x] = ++num;
	int f = 0;
	st[++top] = x;
	if (head[x] == 0 && x == root) {
    
    
		cut[x] = 1;
		v_DCC[x] = ++dcc;
		return;
	}
	for (int i = head[x]; i; i = nex[i]) {
    
    
		int y = to[i];
		if (!dfn[y]) {
    
    
			tarjan(y);
			low[x] = min(low[x], low[y]);
			if (dfn[x] <= low[y]) {
    
    
				// 有可能是割点
				f++;
				if (x != root || f > 1) {
    
    
					// 如果是根节点,需要有至少两个子节点
					cut[x] = 1;
				}
				++dcc;
				while (1) {
    
    
					int t = st[top--];
//					v_DCC[t] = dcc;// 不能这样写,因为一个割点可能属于多个点双连通分量
					vec[dcc].push_back(t);
					if (t == y)break;
				}
			}
			else low[x] = min(low[x], dfn[y]);
		}
	}
}
/*
v_DCC缩点:
	设图中有p个割点,k个点双连通分量,那么新图中应该有p+k割点

	将每个割点与它所在的点双连通分量连边,就得到了新图
*/
void rebuild() {
    
    
	for (int i = 1; i <= n; i++) {
    
    
		if (cut[i]) {
    
    
			new_id[i] = ++id;
		}
	}
	for (int i = 1; i <= dcc; i++) {
    
    
		// 遍历所有的点双连通分量
		for (int j : vec[i]) {
    
    
			if (cut[j]) {
    
    
				add1(new_id[j] + dcc, i);
				add1(i, new_id[j] + dcc);
			}
			else v_DCC[j] = i;// 非割点的点所属的点双连通分量
		}
	}
}
int main()
{
    
    
	init();
	cin >> n >> m;
	while (m--) {
    
    
		int a, b;
		cin >> a >> b;
		add(a, b), add(b, a);
	}
	for (int i = 1; i <= n; i++) {
    
    
		if (!dfn[i]) {
    
    
			root = i;
			tarjan(i);
		}
	}
	rebuild();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43701790/article/details/105387827