/*
雙連通 縮點 橋 LCA
題意:給一個無向連通圖,問每次新加進一條邊後,圖中橋的數目。 重邊算一條。
思路:求雙連通分量,利用並查集縮點,形成一棵樹,樹邊肯定都是橋,然後每對點x,y,找原圖中x,y點對應的新圖中的點,如果不是一個點,則向上>找它們的LCA,因為它們之間連了一條邊,所以這些點到它們的LCA之間的邊都不是割邊了,找LCA時,先將兩點上升到同一層次,然後一起再向上找父親節點
,其間遇到橋就把橋的標記刪除,並且答案減1
思路還是挺簡單的,不過我做了很久,把調試過程記錄下來吧。準確來說的對拍調試過程。
1. ans是當前橋的數目。兩種方法,一種是在Tarjan裡面if (dfn[u] <= low[v]) ans++;第二種是直接ans = Order - 1; Order表示連通塊的個數,>因為縮點之後是一棵樹,那樹的沒一條邊都是橋,所以橋數 = 點數 - 1.
2. 題目沒有說當有重邊的時候要怎麼辦,實驗表明,當有重邊的時候只算一條。那Tarjan的時候有兩個辦法防止無盡迴圈,一是在讀邊的時候就注意>有沒有重邊,則在Tarjan裡面可以用visit[]來記錄某邊是否已經用過;而是改邊判為點判,即記錄點的父節點,然後遞迴之前判斷一下。
感覺還是第二種比較省事。這裡記一下代碼:
for(int k = head[u]; k != -1; k = edge[k].next) {
int v = edge[k].v;
if(fa[u] == v) continue; //!!
if(!dfn[v]) {
fa[v] = u;...
....
3. 建縮點之後的樹還是挺簡單的,可以當作模板了。
4. 樸素lca的各個語句的位置弄混了...
5. 下面這句調試語句棒=幫了很多忙
for(int i = 1; i <= n; i++) printf("# %d\t%d\t%d\n", i, belong[i], deep[belong[i]]); //查了這裡才知道
*/
#include <stdio.h>#include <algorithm>#include <string.h>using namespace std;#define debug printf("!\n")#define MAXN 200005#define MAXM 400005struct Edge { int v, next; } edge[MAXM * 2];int head[MAXN], low[MAXN], dfn[MAXN], belong[MAXN], father[MAXN], deep[MAXN];bool in_stack[MAXN], ok[MAXN], visit[MAXM * 2], used[MAXN];int edge_num, Index, Order, S_top, ans , n;int S[MAXN * 2];int fa[MAXN];void add_edge(int u, int v){ edge[edge_num].v = v; edge[edge_num].next = head[u]; head[u] = edge_num++;}void init(){ edge_num = 0; Index = 1; Order = 0; S_top = 0; ans = 0; memset(head, -1, sizeof(head)); memset(dfn, 0 , sizeof(dfn)); memset(low, 0 , sizeof(low)); memset(visit, false, sizeof(visit)); memset(deep, -1, sizeof(deep)); memset(in_stack, false, sizeof(in_stack)); memset(used, false, sizeof(used)); memset(fa, 0, sizeof(fa)); //有用嗎?????}void Tarjan(int u){ S[S_top++] = u; in_stack[u] = true; dfn[u] = low[u] = Index++; for(int k = head[u]; k != -1; k = edge[k].next) { //if(!visit[k]) { //visit[k] = visit[k^1] = true; int v = edge[k].v; if(fa[u] == v) continue; //還有這裡。。。 if(!dfn[v]) { fa[v] = u; Tarjan(v); low[u] = min(low[u], low[v]); // if(dfn[u] <= low[v]) ans++; 用這個ans的話在只有一個連通塊的時候會錯??? } else if(in_stack[v]) low[u] = min(low[u], dfn[v]); } if(low[u] == dfn[u]) { int x; do { x = S[--S_top]; belong[x] = Order; in_stack[x] = false; } while(x != u); Order++; }}void build(int root, int f){ for(int k = head[root]; k != -1; k = edge[k].next) { int v = edge[k].v; if(v == f || used[v]) continue; used[v] = true; if(belong[v] == belong[root]) build(v, root); else { father[belong[v]] = belong[root]; build(v, root); } }}int get_depth(int node){ if(-1 != deep[node]) return deep[node]; return deep[node] = 1 + get_depth(father[node]);}void Query(int x, int y){ if(deep[x] < deep[y]) swap(x, y); while(deep[x] > deep[y]) { ans -= (!ok[x]); ok[x] = true; x = father[x]; //之前把這一句放在while開始,當然錯啦!! } if(x == y) return ; while(true) { ans -= (!ok[x]) + (!ok[y]); ok[x] = ok[y] = true; x = father[x], y = father[y]; //位置很重要,不確定的時候類比一下即可。 if(x == y) return ; }}int main(){ int Case = 0, query, x, y, m; while(scanf("%d%d", &n, &m) != EOF && n) { init(); while(m--) { scanf("%d%d", &x, &y); add_edge(x, y); //先預設沒有重邊了..結果題目是有重邊的 add_edge(y, x); } Tarjan(1); ans = Order - 1; build(1, -1); deep[belong[1]] = 0; //belong[1] != 0!! for(int i = 0; i < Order; i++) if(-1 == deep[i]) get_depth(i); //for(int i = 1; i <= n; i++) printf("# %d\t%d\t%d\n", i, belong[i], deep[belong[i]]); //查了這裡才知道 for(int i = 0; i < Order; i++) ok[i] = false; printf("Case %d:\n", ++Case); scanf("%d", &query); while(query--) { scanf("%d%d", &x, &y); Query(belong[x], belong[y]); printf("%d\n", ans); } printf("\n"); } return 0;}