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 → v 的一个环,那么
环上所有的边都不是桥了,这是显而易见的。
求公共祖先节点的时候利用了
并查集 压缩路径 的一个思想,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;
}