题目:
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;
}