POJ - 3694 Network tarjan缩点+lca

题目:

https://vjudge.net/problem/POJ-3694

大意:

给定一张N个点M条边的无向连通图,然后执行Q次操作,每次向图中添加一条边,并且询问当前无向图中“桥”的数量。

 

只有当添加的边跨过桥的时候,桥才会消失

对题目中的每个边双联通分量缩点,得到一棵树,然后新加入的边的两端假如位于树的两个不同节点上,此时桥减少。

在新加入的树上可以使用并查集再进一步缩点,因为每个桥的对答案的贡献只能算一次,每当一个桥的贡献用完之后,其子节点需要合并到父节点上。

再具体一点就是 :

用tarjan求出桥----用dfs/bfs缩点(跟着边走,遇到桥continue)--- 缩点之后变成树,在树上预处理lca --- 添加桥,假如桥的两端处在不同的点上,那么求他们的lca并且求减少的桥的数量

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
const int maxn = 1e5 + 7, maxm = 2e5 + 7;
const int INF = 0x3f3f3f3f;
int n, m, tot, id, blo_id, blo_tot;

int Head[maxn], To[maxm << 1], Nxt[maxm << 1];//建边
int dfn[maxn], low[maxn], bridge[maxm << 1]; //dfn low是tarjan用的,bridge记录边是否为桥
int belong[maxn]; //belong[i]表示的是i点缩点之后属于哪个大点
int blo_Head[maxn], blo_Nxt[maxm << 1], blo_To[maxm << 1];//这个是缩点之后大点的边
int dep[maxn], dp[maxn][20], mx_len, fat[maxn];
//dep dp mx_len: lca用的   fat:并查集用的
void init() {
	tot = id = blo_tot = 1;
	blo_id = 0;
	memset(Head, 0, sizeof(Head)); 
	memset(dfn, 0, sizeof(dfn));
	memset(bridge, 0, sizeof(bridge));
	memset(belong, 0, sizeof(belong));
	memset(blo_Head, 0, sizeof(blo_Head));
	memset(dep, 0, sizeof(dep));
}
void add_edge(int fro, int to) {
	Nxt[++tot] = Head[fro];
	To[tot] = to;
	Head[fro] = tot;
}
void add_blo(int fro, int to) { 
	blo_Nxt[++blo_tot] = blo_Head[fro];
	blo_To[blo_tot] = to;
	blo_Head[fro] = blo_tot;
}
void tarjan(int now,int in_i) { //tarjan求桥
	dfn[now] = low[now] = ++id;
	for (int i = Head[now]; i; i = Nxt[i]) {
		int &to = To[i];
		if (!dfn[to]) {
			tarjan(to,i);
			low[now] = min(low[now], low[to]);
			if (low[to]>dfn[now]) {
				bridge[i] = bridge[i ^ 1] = 1; //记录桥
			}
		}
		else if (i != (in_i ^ 1)) {//可能会两点多边 
			low[now] = min(low[now], dfn[to]);
		}
	}
}
queue<int>Q;
void make_blo(int x) { //bfs缩点
	Q.push(x);
	while (!Q.empty()) {
		int now = Q.front();
		Q.pop();
		belong[now] = blo_id; //记录这个点属于哪个大点
		for (int i = Head[now]; i; i = Nxt[i]) {
			int &to = To[i];
			if (bridge[i]||belong[to]) continue; //遇到桥或者已经走过了就continue
			//belong[to] = blo_id;
			Q.push(to);
		}
	}
}
void bfs() { //bfs预处理倍增lca
	while (!Q.empty()) Q.pop();
	Q.push(1);
	dep[1] = 1;
	while (!Q.empty()) {
		int now = Q.front();
		Q.pop();
		for (int i = blo_Head[now]; i; i = blo_Nxt[i]) {
			int &to = blo_To[i];
			if (dep[to]) continue;
			dep[to] = dep[now] + 1;
			dp[to][0] = now;
			for (int j = 1; j <= mx_len; j++)
				dp[to][j] = dp[dp[to][j - 1]][j - 1];
			Q.push(to);
		}
	}
}
int lca(int x, int y) { //求lca
	if (dep[x] > dep[y]) swap(x, y);
	for (int i = mx_len; i >= 0; i--) {
		if (dep[dp[y][i]] >= dep[x]) y = dp[y][i];
	}
	if (x == y) return x;
	for (int i = mx_len; i >= 0; i--) {
		if (dp[y][i] != dp[x][i]) {
			y = dp[y][i];
			x = dp[x][i];
		}
	}
	return dp[x][0];
}
int trace(int x) {//并查集
	return fat[x] == x ? x : fat[x] = trace(fat[x]);
}
int main() {
	int T; 
	T = 0;
	while (cin >> n >> m && n) {
		init();
		int fro, to;
		for (int i = 1; i <= m; i++) {
			scanf("%d %d", &fro, &to);
			add_edge(fro, to);
			add_edge(to, fro);
		}
		tarjan(1,0);  //记录桥
		for (int i = 1; i <= n; i++) {
			if (!belong[i]) {
				blo_id++; //blo_id表示的是大点的id
				make_blo(i); 
			}
		}
		for (int i = 2; i <= tot; i++) { //桥的两边属于不同的大点,建边
			if (bridge[i]) add_blo(belong[To[i]], belong[To[i ^ 1]]);
		}
		mx_len = (int)(log(blo_id) / log(2)) + 1; //倍增lca
		bfs(); //倍增lca。。。
		int q, ba, bb, f; cin >> q;
		printf("Case %d:\n", ++T);
		int ans = blo_id - 1; //当前的桥的数量
		for (int i = 1; i <= blo_id; i++) fat[i] = i; //并查集再缩点。。
		while (q--) {
			scanf("%d %d", &fro, &to);
			ba = belong[fro], bb = belong[to];
			f = lca(ba, bb);
			ba = trace(ba), bb = trace(bb);
			while (fat[ba] != fat[f]) {
				ans -= 1;
				fat[ba] = dp[ba][0]; //和父节点合并
				ba = trace(ba);
			}
			while (fat[bb]!=fat[f]) {
				ans -= 1;
				fat[bb] = dp[bb][0];
				bb = trace(bb);
			}
			printf("%d\n", ans);
		}
		printf("\n");
	}
	return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值