POJ 3694 Network Tarjan求边双连通+LCS+并查集

POJ 3694 Network Tarjan求边双连通+LCS+并查集

题目链接POJ 3694 Network 
题意:给定 N个顶点M条边的无向图。N(1 ≤ N ≤ 100,000) and M(N - 1 ≤ M ≤ 200,000). 数据保证 图是连通的。 然后Q个操作(Q < 1000), 每次往图中添加一条边,每次操作之后给出图中还有多少个桥(割边)。
思路:这是一道很有意思的题目!首先对原图进行边双连通缩点,求出DAG,因为初始的无向图是连通的,那么求出来的DAG是一棵树(PS:为了以后求LCA,我需要保存每个节点的父节点和深度),树上的边全是桥,这是无向连通图的性质嘛。
求出DAG树之后,我们理解为我们新构建了一个 有向图
然后每次添加一条边(u, v),如果两个顶点在原图中的同一个边双连通,毫无疑问,不需要处理。反之, 就向上找最近公共祖先节点设为x,并把沿途的节点加入并查集中,因为添加一条边,相当于在树上增加一条边,即形成了 u → x → 的一个环,那么 环上所有的边都不是桥了,这是显而易见的。
求公共祖先节点的时候利用了 并查集 压缩路径 的一个思想,DAG图中,同一个强连通分量中,深度高的节点直接跳转到他的祖先的位置(我让祖先一定是同一个强连通分量中深度最低的节点,并查集中 合并的时候可以保证到这点),这样就不必要一个个向上走了,这个在 代码中有所体现。
这样就是344MS水过了。 微笑
测试数据 可以参考 Discuss 提供的测试数据。 注意, 无向图边数要开双倍,时刻记住,谨记谨记~
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <algorithm>
using namespace std;

#define FIN             freopen("input.txt","r",stdin)
#define FOUT            freopen("output.txt","w",stdout)
#define fst             first
#define snd             second
typedef __int64			LL;
typedef pair<int, int>	PII;

const int MAXN = 100000 + 5;
const int MAXM = 200000 + 5;

struct Edge {
	int v, next;
	Edge() {}
	Edge(int v, int next) : v(v), next(next) {}
} edge[MAXM << 1];
struct TNode {
	int fa, depth;
	TNode() {}
	TNode(int fa, int depth) : fa(fa), depth(depth) {}
} tree[MAXN];
int head[MAXN], tot;
int low[MAXN], dfn[MAXN], belong[MAXN], stack[MAXN], top, Index, bridge, block;
bool inStack[MAXN];
bool vis[MAXN];
int par[MAXN];
int N, M, Q;

void init() {
	block = bridge = tot = Index = top = 0;
	memset(head, -1, sizeof(head));
	memset(dfn, 0, sizeof(dfn));
	memset(inStack, false, sizeof(inStack));
	memset(vis, false, sizeof(vis));
}
void add_edge(int& u, int& v) {
	edge[tot] = Edge(v, head[u]);
	head[u] = tot++;
}
void Tarjan(int u, int pre) {
	int v;
	low[u] = dfn[u] = ++Index;
	stack[top++] = u;
	inStack[u] = true;
	bool flag = false; // 跳过反向边
	for (int i = head[u]; ~i; i = edge[i].next) {
		v = edge[i].v;
		if (v == pre && !flag) {
			flag = true;
			continue;
		}
		if (!dfn[v]) {
			Tarjan(v, u);
			if (low[u] > low[v]) low[u] = low[v];
			if (low[v] > dfn[u]) {
				bridge ++;
			}
		} else if (inStack[v] && low[u] > dfn[v]) low[u] = dfn[v];
	}
	if (low[u] == dfn[u]) {
		block++;
		do {
			v = stack[--top];
			inStack[v] = false;
			belong[v] = block;
		} while (u != v);
	}
}
void build_tree(const int u, const int pre, const int depth) {
	int v;
	vis[u] = true;
	tree[belong[u]] = TNode(belong[pre], depth);
	for (int i = head[u]; ~i; i = edge[i].next) {
		v = edge[i].v;
		if (vis[v]) continue;
		if (v == pre) continue;
		build_tree(v, u, belong[u] == belong[v] ? depth : depth + 1);
	}
}
int Find(const int x) {
	return x == par[x] ? x : (par[x] = Find(par[x]));
}
void Union(const int& a, const int& b) {
	int pa = Find(a), pb = Find(b);
	if (pa == pb) return;
	par[pa] = pb;
	Find(a), Find(b);
}
void lca(int a, int b) {
	if (Find(a) == Find(b)) return;
	while (tree[a].depth < tree[b].depth) {
		if (Find(tree[b].fa) != Find(b)) bridge --;
		Union(b, tree[b].fa);
		b = Find(b);
	}
	while (tree[a].depth > tree[b].depth) {
		if (Find(tree[a].fa) != Find(a)) bridge --;
		Union(a, tree[a].fa);
		a = Find(a);
	}
	while (a != b) {
		if (Find(tree[a].fa) != Find(a)) bridge --;
		if (Find(tree[b].fa) != Find(b)) bridge --;
		Union(a, tree[a].fa);
		Union(b, tree[b].fa);
		a = Find(a);
		b = Find(b);
	}
}

int main() {
#ifndef ONLINE_JUDGE
	FIN;
#endif // ONLINE_JUDGE
	int u, v, cas = 0;
	while (~scanf("%d %d", &N, &M) && N) {
		init();
		for (int i = 0; i < M; i++) {
			scanf("%d %d", &u, &v);
			add_edge(u, v);
			add_edge(v, u);
		}
		Tarjan(1, 0);
		build_tree(1, 1, 1);
		for (int i = 1; i <= block; i++) par[i] = i;
		scanf("%d", &Q);
		printf("Case %d:\n", ++cas);
		while (Q--) {
			scanf("%d %d", &u, &v);
			lca(belong[u], belong[v]);
			printf("%d\n", bridge);
		}
		puts("");
	}
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值