双联通分量及例题

点双连通和边双连通
连通的概念:在无向图中,所有点能互相到达
连通分量:互相联通的子图
点双连通:删掉一个点之后,图仍联通
边双连通:删掉一条边之后,图仍联通
tarjan 算法:
该算法是R.Tarjan发明的。对图深度优先搜索, dfn[i]为第i个结点在搜索树中的深度,low[i]为第i个结点的子树的所有儿子连接到的最上面的结点层数。根据定义,则有:
一个顶点u是割点,当且仅当满足(1)或(2) 
(1) u为树根,且u有多于一个子树。 
(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)。 
一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)

求双连通分量
对于点双连通分量,实际上在求割点的过程中就能顺便把每个点双连通分量求出。建立一个栈,存储当前双连通分量,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分量,其余点和每条边只属于且属于一个点双连通分量支。 
(这种还没有实现过,不过我认为显然如果把割点标记出来,跑dfs也能求出点双连通分支,虽然代码量会上升,不过还挺好打的,下面例题二类似)
对于边双连通分量,求法更为简单。只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分量。桥不属于任何一个边双连通分量,其余的边和每个顶点都属于且只属于一个边双连通分量。
模板:

int root, cnt;
int vis[maxn], dfn[maxn], low[maxn];
bool cut[maxn];
//vector<pair<int, int>>bridge;

void dfs(int u, int fa)
{
    int son=0;
    vis[u]=1;
    dfn[u]=low[u]=++cnt;
    for (int i=0; i<G[u].size(); i++)
    {
        int v=G[u][i];
        if (v==fa) continue;
        if (vis[v]==1) low[u]=min(low[u], dfn[v]); 
        if (vis[v]==0)
        {
            dfs(v, u);
            son++;
            low[u]=min(low[u], low[v]);
            if ( (u==root && son>1) || (u!=root && low[v]>=dfn[u]))
            {
                cut[u]=true;
                //if(low[v] > dfn[u]) bridge.push_back({u, v}); //(u, v) 是桥
            }
        }
    }
    vis[u]=2;
}

void tarjan_init()
{
    memset(vis, 0, sizeof(vis));
    memset(cut, 0, sizeof(cut));
    cnt=0; root=1;
    //bridge.clear();
}

例题:

poj-1144

题目大意:
给出一个无向图,求出有多少个割点。
输入:
有若干组测试数据。每一组测试数据的第一行有一个整数 n,表示有 n
(1<=n<100)个点,n=0 时测试数据结束。接下来有若干行,每一行第一个整
数 u 表示这一行描述的是以 u 为起点的边,接下来有若干个整数 vi 表示有一条
边 u-vi,u=0 时表示这一组测试数据结束。
输出:
对于每一组测试数据,输出一个整数,即有多少个割点。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
#define N 1005
vector<int>a[N],m;
int n, cnt, root;
char str[N];
int dfn[N], low[N], vis[N], cut[N];
void init() {
	cnt = 0; root = 1;
	memset(cut, 0, sizeof(cut));
	memset(vis, 0, sizeof(vis));
	for (int i = 1; i <= n; i++)
		a[i].clear();
}
void dfs(int u, int fa) {
	int child = 0;
	cut[u] = 1;
	dfn[u] = low[u] = ++cnt;
	for (int i = 0; i < a[u].size(); i++) {
		int v = a[u][i];
		if (v == fa)continue;
		if (cut[v]==0) {
			dfs(v, u);
			child++;
			low[u] = min(low[u], low[v]);
			if ((u == root && child > 1) || (u != root && low[v] >= dfn[u])) {
				vis[u] = 1;
			}
		}
		if(cut[v]==1) {
			low[u] = min(low[u], dfn[v]);
		}
	}
	cut[u] = 2;
}
void deal()
{
	m.clear();
	int lens = strlen(str), now = 0;
	for (int i = 0; i<lens; i++)
		if (str[i] == ' ')
		{
			m.push_back(now);
			now = 0;
		}
		else now = now * 10 + (str[i] - '0');
		m.push_back(now);
}
int main() {
	int ans;
	while (scanf("%d\n", &n)!=EOF) {
		if (n == 0)break;
		init();
		ans = 0;
		while (1) {
			scanf("%[^\n]s", str); getchar();
			deal();
			for (int i = 1; i < m.size(); i++) {
				a[m[0]].push_back(m[i]);
				a[m[i]].push_back(m[0]);
			}
			if (m[0] == 0)break;
		}
		dfs(1, -1);
		for (int i = 1; i <= n; i++) {
			if (vis[i])
				ans++;
		}
		cout << ans << endl;
	}
	return 0;
}

poj-1523

题解: 
先求割点,然后枚举每一个割点裸 dfs 求连通分量数量。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<vector>
using namespace std;
#define N 1100
int s, e, n;
int cnt, root;
int dfn[N], low[N], re[N], vis[N];
int res[N];
int flag;
vector<int>a[N];
void init() {
	root = 1; cnt = 0;
	memset(vis, 0, sizeof(vis));
	memset(dfn, 0, sizeof(dfn));
}
void dfs(int u, int fa) {
	int child;
	child = 0;
	dfn[u] = low[u] = ++cnt;
	for (int i = 0; i < a[u].size(); i++) {
		int v = a[u][i];
		if (v == fa)continue;
		if (!dfn[v]) {
			child++;
			dfs(v, u);
			low[u] = min(low[u], low[v]);
			if ((u == root && child > 1) || (u != root && low[v] >= dfn[u])) {
				flag = 1;
				vis[u] = 1;
			}
		}
		else {
			low[u] = min(dfn[v], low[u]);
		}
	}
}
void bfs(int u) {
	queue<int>q;
	q.push(u);
	while (!q.empty()) {
		int cmt = q.front();
		q.pop();
		for (int i = 0; i < a[cmt].size(); i++) {
			int to = a[cmt][i];
			if (!res[to]) {
				res[to] = 1;
				q.push(to);
			}
		}
	}
}
int find(int u) {
	int ans = 0;
	memset(res, 0, sizeof(res));
	res[u] = 1;
	for (int i = 1; i <= n; i++) {
		if (!res[i]) {
			ans++;
			res[i] = 1;
			bfs(i);
		}
	}
	return ans;
}
int main() {
	int t = 1;
	while (t++) {
		flag = 0;
		n = -1;
		init();
		cin >> s;
		if (s == 0)break;
		cin >> e;
		a[s].push_back(e);
		a[e].push_back(s);
		n = max(n, max(s, e));
		while (1) {
			cin >> s;
			if (s == 0)break;
			cin >> e;
			a[s].push_back(e);
			a[e].push_back(s);
			n = max(n, max(s, e));
		}
		dfs(root, -1);
		if (!flag) {
			cout << "Network #" << t-1 << endl;
			cout << "  No SPF nodes" << endl << endl;
			for (int i = 1; i <= n; i++)
				a[i].clear();
			continue;
		}
		cout << "Network #" << t-1 << endl;
		for (int i = 1; i <= n; i++) {
			if (vis[i]) {
				cout << "  SPF node " << i << " leaves " << find(i) << " subnets" << endl;
			}
		}
		cout << endl;
		for (int i = 1; i <= n; i++)
			a[i].clear();
	}
	return 0;
}

poj-3694

求双连通分量,利用并查集缩点,形成一棵树,树边肯定都是桥,然后每对点x,y,找原图中x,y点对应的新图中的点,如果不是一个点,则向上找它们的LCA,因为它们之间连了一条边,所以这些点到它们的LCA之间的边都不是割边了,找LCA时,先将两点上升到同一层次,然后一起再向上找父亲节点,其间遇到桥就把桥的标记删除,并且答案减1。
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define N 100005
int n, m, s, e, k, root;
int cnt;
int head[N], low[N], dfn[N], vis[N], cmt[N], parent[N];
int df[N];
int ans;
struct eg {
	int to, st, next;
}a[4*N];
void dfs(int u, int fa, int index) {
	df[u] = index;
	dfn[u] = low[u] = ++cnt;
	vis[u] = 1;
	for (int i = head[u]; i != -1; i = a[i].next) {
		int v = a[i].to;
		if (v == fa)continue;
		if (vis[v] == 1) {
			low[u] = min(low[u], dfn[v]);
		}
		if (vis[v] == 0) {
			dfs(v, u, index + 1);
			parent[v] = u;
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u]) {
				cmt[v] = 1;
				ans++;
			}
		}
	}
	vis[u] = 2;
}
void init() {
	root = 1; cnt = 0; ans = 0;
	memset(head, -1, sizeof(head));
	memset(vis, 0, sizeof(vis));
	memset(cmt, 0, sizeof(cmt));
}
void add(int  u, int v) {
	a[cnt].st = u;
	a[cnt].to = v;
	a[cnt].next = head[u];
	head[u] = cnt++;
	a[cnt].st = v;
	a[cnt].to = u;
	a[cnt].next = head[v];
	head[v] = cnt++;
}
int find(int u, int v) {
	int sum = 0;
	if (df[u] > df[v])swap(u, v);
	while (df[u] < df[v]) {
		if (cmt[v]) {
			sum++;
			cmt[v] = 0;
		}
		v = parent[v];
	}
	while (u != v) {
		if (cmt[u]) {
			sum++;
			cmt[u] = 0;
		}
		if (cmt[v]) {
			sum++;
			cmt[v] = 0;
		}
		v = parent[v];
		u = parent[u];
	}
	return sum;
}
int main() {
	int nu = 1;
	while (~scanf("%d%d", &n, &m)) {
		if (n == 0 & n == m)break;
		init();
		for (int i = 0; i < m; i++) {
			scanf("%d%d", &s, &e);
			add(s, e);
		}
		cnt = 0;
		dfs(root, -1, 1);
		scanf("%d", &k);
		cout << "Case " << nu++ << ":" << endl;
		for (int i = 0; i < k; i++) {
			scanf("%d%d", &s, &e);
			ans -= find(s, e);
			cout << ans << endl;
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值