「poj 3694」Network (边双连通分量缩点+LCA)

poj 3696
题意:给出一个无向图,动态加边,动态询问桥的条数。

先用 Tarjan 求出桥的条数并且边双连通分量缩点,变为一棵树,然后树上的边就是桥。发现如果加了一条边$(x,y)$,则树上构成一个环,树上的部分都不再是桥。所以可以将这个环又缩成点。具体实现将所有点并到$LCA(x,y)$处,显然可以并查集轻松维护。也可以不求 $ LCA $,将每个$(x,y)$路径上的点并到他的父亲,一样的效果。

本题还有种解法,即只求出最开始桥的条数,然后将搜索树树剖,将桥边权赋值为1,那么加了一条边$(x,y)$,则树上构成一个环,答案将减去树上$(x,y)$部分的权值,并且将$(x,y)$全部置为 $ 0 $

知识点:

1、东西多的加后缀不会写错,比如原图后缀_o, 缩点图后缀_ecc
2、倍增LCA的深度必须$dep(1)=1$

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<set>
#include<queue>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
#define fir first
#define sec second
#define mp make_pair
using namespace std;
int kase = 0;
namespace flyinthesky {
const int MAXN = 100000 + 5, LOGS = 20;
struct edge {
int v, nxt;
} ed_o[200000 * 2 + 5], ed_ecc[200000 * 2 + 5];
int n_o, m_o, en_o, hd_o[MAXN], q;
int low_o[MAXN], dfn_o[MAXN], sz_o, bri_o[200000 * 2 + 5];
int siz_ecc, bl_ecc[MAXN];
int en_ecc, hd_ecc[MAXN];
int f_ecc[MAXN], dis_ecc[MAXN], pre_ecc[MAXN][LOGS + 5], ans; // 加后缀不会写错
void ins_o(int u, int v) {ed_o[++en_o] = (edge){v, hd_o[u]}, hd_o[u] = en_o;}
void ins_ecc(int u, int v) {ed_ecc[++en_ecc] = (edge){v, hd_ecc[u]}, hd_ecc[u] = en_ecc;}
int find_ecc(int x) {return f_ecc[x] == x ? x : f_ecc[x] = find_ecc(f_ecc[x]);}
void tarjan_o(int u, int pre) {
low_o[u] = dfn_o[u] = ++sz_o;
for (int i = hd_o[u]; i >= 0; i = ed_o[i].nxt) {
edge &e = ed_o[i];
if (i != (pre ^ 1)) {
if (!dfn_o[e.v]) {
tarjan_o(e.v, i); // 注意不要写错成 u
low_o[u] = min(low_o[u], low_o[e.v]);
if (low_o[e.v] > dfn_o[u])
bri_o[i] = bri_o[i ^ 1] = 1, ++ans;
} else low_o[u] = min(low_o[u], dfn_o[e.v]);
}
}
}
void dfs1_o(int u, int gg) {
bl_ecc[u] = gg;
for (int i = hd_o[u]; i >= 0; i = ed_o[i].nxt) {
edge &e = ed_o[i];
if (!bl_ecc[e.v] && !bri_o[i]) dfs1_o(e.v, gg);
}
}
void dfs_ecc(int u, int fa) {
dis_ecc[u] = dis_ecc[fa] + 1, pre_ecc[u][0] = fa; // dis_ecc[1] 要为 1
for (int i = 1; i <= LOGS; ++i) pre_ecc[u][i] = pre_ecc[pre_ecc[u][i - 1]][i - 1];
for (int i = hd_ecc[u]; i >= 0; i = ed_ecc[i].nxt) {
edge &e = ed_ecc[i];
if (e.v != fa) {
dfs_ecc(e.v, u);
}
}
}
int LCA_ecc(int a, int b) {
if (dis_ecc[a] < dis_ecc[b]) swap(a, b);
for (int i = LOGS; i >= 0; --i) if (dis_ecc[pre_ecc[a][i]] >= dis_ecc[b]) a = pre_ecc[a][i];
if (a == b) return a;
for (int i = LOGS; i >= 0; --i) if (pre_ecc[a][i] != pre_ecc[b][i]) a = pre_ecc[a][i], b = pre_ecc[b][i];
return pre_ecc[a][0];
}
void clean() {
en_o = -1, ms(hd_o, -1);
ms(low_o, 0), ms(dfn_o, 0), sz_o = 0, ms(bri_o, 0);
siz_ecc = 0, ms(bl_ecc, 0);
en_ecc = -1, ms(hd_ecc, -1);
ms(dis_ecc, 0), ms(pre_ecc, 0), ans = 0;
}
int solve() {
printf("Case %d:\n", ++kase);
clean();
for (int x, y, i = 1; i <= m_o; ++i) scanf("%d%d", &x, &y), ins_o(x, y), ins_o(y, x);
tarjan_o(1, -1); // tarjan 找桥
for (int i = 1; i <= n_o; ++i) // ecc 给点编号
if (!bl_ecc[i]) dfs1_o(i, ++siz_ecc);
for (int u = 1; u <= n_o; ++u) { // ecc 加边
for (int i = hd_o[u]; i >= 0; i = ed_o[i].nxt) {
edge &e = ed_o[i];
if (bl_ecc[u] != bl_ecc[e.v]) ins_ecc(bl_ecc[u], bl_ecc[e.v]); // 注意不要写成原来的点
}
}
dfs_ecc(1, 0); // ecc 上预处理
for (int i = 1; i <= siz_ecc; ++i) f_ecc[i] = i;
scanf("%d", &q);
while (q--) {
int x, y; scanf("%d%d", &x, &y);
int lca = find_ecc(LCA_ecc(find_ecc(bl_ecc[x]), find_ecc(bl_ecc[y])));
int cnt = 0;
int now = find_ecc(bl_ecc[x]);
while (now != lca) f_ecc[now] = lca, now = find_ecc(pre_ecc[now][0]), ++cnt;
now = find_ecc(bl_ecc[y]);
while (now != lca) f_ecc[now] = lca, now = find_ecc(pre_ecc[now][0]), ++cnt;
ans -= cnt;
printf("%d\n", ans);
}
printf("\n");
return 0;
}
}
int main() {
while (scanf("%d%d", &flyinthesky::n_o, &flyinthesky::m_o) && flyinthesky::n_o && flyinthesky::m_o) flyinthesky::solve();
return 0;
}
------ 本文结束 ------