POJ 3694 network tarjan求桥 + LCA + e-DCC缩点 + 并查集优化

题目链接(You can click it.)

题目含义:

有一张无向图,在其中添边。问每次添边之后,还剩多少桥。

前面两篇博文已经说的很明了了吧QAQ,本文说的是带并查集优化解法。

首先想一下有什么可以优化的空间。在求出两个点的 lca 后,需要将两个点向上遍历到 lca 处,并且把路径上的桥边标记为非桥边,但是我们发现, 如果这个路径上本来就有一段路径被标记为了非桥边 ,再次走一遍就重复了,能否去掉重复呢?

答案是肯定的,可以去掉重复。(也就是并查集优化啦~~~)

我们可以做这么一个处理,如果一个点的父路径被标记为了非桥边,就跳过这条边,继续往上(和暴力的向上搜索一样,只是把这个边跳过了),如何跳过呢?利用并查集的思想, 直接将这个点并到它的祖先节点中第一个桥边的位置即可 ,这样就可以遇到跳过非桥边,并且从第一个桥边继续向上搜索。

初始时刻,数组 r o o t [ N ] root[N] root[N]的每个值都是 r o o t [ i ] = i root[i] = i root[i]=i,即每个点向上走的第一个桥边就是到父节点的边,一旦经过了一条桥边并将其置为非桥边,就将 r o o t root root的值修改,如果 x x x是子节点, y y y是父节点,那么就是 r o o t [ x ] = y root[x]=y root[x]=y,或者 r o o t [ x ] = r o o t [ y ] root[x]=root[y] root[x]=root[y],或 r o o t [ x ] = f i n d ( y ) root[x] = find(y) root[x]=find(y) f i n d find find是并查集中的查询函数。

除此以外还需要一些小更改,判断是否到达 lca 点不能再用等于了,因为有可能路径压缩后,直接指向了一个 lca 的祖先节点,因此需要判断深度,如果深度小于或等于 lca 了,说明到 lca 了。然后就是每次向上搜索到父节点时,都需要执行一次并查集中的查询函数,查询自身的根节点,也就是可以一下子最多走到哪里,因为我们路径压缩使得图中只剩下了桥边,并且初始时刻,缩点建图后图中也只有桥边,因此不用判断经过的边是否为桥(一定是桥边了),直接就可以进行 a n s = a n s − 1 ans = ans - 1 ans=ans1(因为此时它是桥边,而后变为非桥边了)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define mem(a, b) memset(a, b, sizeof a)
using namespace std;
const int N = 100100, M = 400100;
int head[N], to[M], nex[M], cnt;
int head1[N], to1[M], nex1[M], cnt1;
int dfn[N], low[N], num;
int e_DCC[N], dcc;
int f[N][20], d[N], root[N];
bool bridge[M];
int n, m;
void init(){
    
    
	mem(head, 0), mem(nex, 0), cnt = 1;
	mem(head1, 0), mem(nex1, 0), cnt1 = 1;
	mem(dfn, 0), mem(low, 0), num = 0;
	mem(e_DCC, 0), dcc = 0;
	mem(f, -1), mem(bridge, 0), mem(d, 0);
	for (int i = 0; i < N; i++)root[i] = i;
}
void add(int x, int y){
    
    
	to[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void add1(int x, int y){
    
    
	to1[++cnt1] = y;
	nex1[cnt1] = head1[x];
	head1[x] = cnt1;
}
void tarjan(int x, int ind){
    
    //  tarjan求桥
	dfn[x] = low[x] = ++num;
	for (int i = head[x]; i; i = nex[i]){
    
    
		int y = to[i];
		if (!dfn[y]){
    
    
			tarjan(y, i);
			low[x] = min(low[x], low[y]);
			if (dfn[x] < low[y]){
    
    
				bridge[i] = bridge[i ^ 1] = 1;
			}
		}
		else if (ind != (i ^ 1))low[x] = min(low[x], dfn[y]);
	}
}
void dfs(int x){
    
    // dfs求连通块(求边双连通分量)
	e_DCC[x] = dcc;
	for (int i = head[x]; i; i = nex[i]){
    
    
		int y = to[i];
		if (bridge[i] || e_DCC[y])continue;
		dfs(y);
	}
}
void DFS(int x, int depth){
    
    // dfs求每个边双连通分量的深度(将每个边双连通分量看做一个点)
	d[x] = depth;
	for (int i = head1[x]; i; i = nex1[i]){
    
    
		int y = to1[i];
		if (!d[y]){
    
    
			DFS(y, depth + 1);
			f[y][0] = x;
		}
	}
}
int lca(int a, int b){
    
    // 求LCA
	if (d[a] < d[b]){
    
    
		a = a ^ b, b = a ^ b, a = a ^ b;
	}
	for (int i = 18; i >= 0; i--){
    
    
		if (d[f[a][i]] >= d[b])a = f[a][i];
	}
	if (a == b)return a;
	for (int i = 18; i >= 0; i--){
    
    
		if (f[a][i] != f[b][i]){
    
    
			a = f[a][i], b = f[b][i];
		}
	}
	return f[a][0];
}
int find(int a){
    
    // 并查集 查询函数
	return a == root[a] ? a : root[a] = find(root[a]);
}
int main()
{
    
    
	int k = 0;
	while (~scanf("%d %d", &n, &m) && n && m){
    
    
		init();
		while (m--){
    
    
			int a, b;
			scanf("%d %d", &a, &b);
			add(a, b), add(b, a);
		}
		for (int i = 1; i <= n; i++){
    
    
			if (!dfn[i])tarjan(i, 0);
		}
		for (int i = 1; i <= n; i++){
    
    // 求连通块
			if (!e_DCC[i]){
    
    
				++dcc;
				dfs(i);
			}
		}
		int ans = dcc - 1;
		for (int i = 2; i <= cnt; i++){
    
    // 缩点 构建新图
			int x = to[i];
			int y = to[i ^ 1];
			if (e_DCC[x] != e_DCC[y]){
    
    
				add1(e_DCC[x], e_DCC[y]);
			}
		}
		for (int i = 1; i <= dcc; i++){
    
    // 求深度
			if (!d[i])
				DFS(i, 1);
		}
		for (int i = 1; i <= 18; i++){
    
    // 预处理数组f  求LCA备用
			for (int j = 1; j <= dcc; j++){
    
    
				f[j][i] = f[f[j][i - 1]][i - 1];
			}
		}
		printf("Case %d:\n", ++k);
		int Q;
		cin >> Q;
		while (Q--){
    
    
			int x, y;
			scanf("%d %d", &x, &y);
			x = e_DCC[x], y = e_DCC[y];
			if (x != y){
    
    
				int lc = lca(x, y);
				x = find(x);
				y = find(y);
				while (d[x] > d[lc]){
    
    
					ans--;
//					root[x] = root[f[x][0]];
					root[x] = find(f[x][0]);
					x = f[x][0];
					x = find(x);
				}
				while (d[y] > d[lc]){
    
    
					ans--;
//					root[y] = root[f[y][0]];
					root[y] = find(f[y][0]);
					y = f[y][0];
					y = find(y);
				}
			}
			printf("%d\n", ans);
		}
		puts("");
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43701790/article/details/105353190
今日推荐