tarjan算法與無向圖的連通性(割點,橋,雙連通份量,縮點)

基本概念node

給定無向連通圖G = (V, E)
割點:
對於x∈V,從圖中刪去節點x以及全部與x關聯的邊以後,G分裂爲兩個或兩個以上不相連的子圖,則稱x爲割點
割邊(橋)
若對於e∈E,從圖中刪去邊e以後,G分裂成兩個不相連的子圖,則稱e爲G的橋或割邊ios

時間戳
在圖的深度優先遍歷過程當中,按照每一個節點第一次被訪問的時間順序,依次給予N個節點1~N的整數標記,該標記被稱爲「時間戳」,記爲dfn[x]c++

搜索樹
在無向連通圖中任選一個節點出發進行深度優先遍歷嗎,每一個節點只訪問一次。全部發生遞歸的邊(x, y)構成一棵樹,稱爲「無向連通圖的搜索森林」。通常無向圖的各個連通塊的搜索樹構成無向圖的「搜索森林」。對於深度優先遍歷出的搜索樹,按照被遍歷的次序,標記節點的時間戳git

追溯值
追溯值low[x]。設subtree(x)表示搜索樹中以x爲根的子樹。low[x]定義爲如下節點時間戳的最小值
low[u]定義爲u或者u的子樹中可以經過非父子邊(父子邊就是搜索樹上的邊)追溯到的最先的節點的時間戳
即:
1.subtree(x)中的節點
2.經過一條不在搜索樹上的邊,可以到達subtree(x)的節點算法

爲了計算low[x],應該先令low[x] = dfn[x],而後考慮從x出發的每條邊(x, y):
若在搜索樹上x是y的父節點,則令low[x] = min(low[x], low[y])
若無向邊(x, y)不是搜索樹上的邊,則令low[x] = min(low[x], dfn[y])數組

橋的斷定法則ide

無向邊(x, y)是橋,當且僅當搜索樹上存在x的一個子節點y,知足:
dfn[x] < low[y]
根據定義,dfn[x] <low[y]說明從subtree(y)出發,在不通過(x, y)的前提下,無論走哪條邊,都沒法到達x或比x更早訪問的節點。若把(x, y)刪除,則subtree(y)就好像造成了封閉的環境,與節點x沒有邊相連,圖斷成了兩部分,(x, y)爲橋
反之,若不存在這樣的子節點x和y,使得dfn[x] < low[y],這說明每一個subtree(y)都能繞行其餘邊到x或比x更早的節點,(x, y)也就不是橋ui

橋必定是搜索樹中的邊,而且一個簡單環中的邊必定都不是橋spa

須要注意的是, 由於咱們要遍歷的是無向圖, 因此從每一個節點x出發,總能訪問到他的父節點fa,根據low的計算方法,(x, fa)屬於搜索樹上的邊,且fa不是x的子節點,故不能用fa的時間戳來更新low[x]。
若是僅記錄每一個節點的父節點,會沒法處理重邊的狀況——當x與fa之間有多條邊時,(x, fa)必定不是橋,在這些重複計算中,只有一條邊在搜索樹上,其餘的幾條都不算,故有重邊時,dfn[fa]不能用來更新low[x]
解決方案是:記錄「遞歸進入每一個節點的邊的編號」。編號可認爲是邊在鄰接表中儲存下標位置。把無向圖的每條邊看作雙向邊,成對存儲在下標"2和3","4和5","6和7"...處。若沿着編號i的邊遞歸進入節點x,則忽略從x出發的編號爲i xor 1的邊,經過其餘邊計算low[x]便可.net

(補充:^的成對變換

對於非負整數n
當n爲偶數時,n xor 1等於n+1
當n爲奇數時,n xor 1等於n-1
所以「0與1」「2與3」「3與5」……關於xor 1運算構成了成對變換
這一性質常常用於圖論鄰接表中邊集的存儲。在具備無向邊(雙向邊)的圖中把一對正反方向的邊分別儲存在鄰接表數組的第n與n+1個位置(其中n爲偶數),就能夠經過xor 1運算得到與當前邊(x, y)反向的邊(y, x)的存儲位置

在程序開始時,初始化變量tot = 1。這樣每條無向邊當作的兩條有向邊會成對存儲在ver和edge數組的下表「2和3」「4和5」「6和7」……的位置上。經過對下表xor 1操做,就能夠直接定位到與當前反向的邊。換句話說,若是ver[i]是第i條邊的終點,那麼ver[i ^ 1]就是第i條邊的起點)

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 const int maxn = 100086;  4 struct node {  5     int y, net;  6 }e[maxn << 1];  7 int lin[maxn], len = 1;  8 bool bridge[maxn << 1];  9 int dfn[maxn], low[maxn]; 10 int n, m, num; 11 
12 inline int read() { 13     int x = 0, y = 1; 14     char ch = getchar(); 15     while(!isdigit(ch)) { 16         if(ch == '-') y = -1; 17         ch = getchar(); 18  } 19     while(isdigit(ch)) { 20         x = (x << 1) + (x << 3) + ch - '0'; 21         ch = getchar(); 22  } 23     return x * y; 24 } 25 
26 inline void insert(int xx, int yy) { 27     e[++len].net = lin[xx]; 28     e[len].y = yy; 29     lin[xx] = len; 30 } 31 
32 inline void tarjan(int x, int in_edge) { 33     dfn[x] = low[x] = ++num; 34     for(int i = lin[x]; i; i = e[i].net) { 35         int to = e[i].y; 36         if(!dfn[to]) { 37  tarjan(to, i); 38             low[x] = min(low[x], low[to]); 39             if(low[to] > dfn[x]) 40                 bridge[i] = bridge[i ^ 1] = true; 41  } 42         else if(i != (in_edge ^ 1)) 43             low[x] = min(low[x], dfn[to]); 44  } 45 } 46 
47 int main() { 48     n = read(), m = read(); 49     len = 1; 50     for(int i = 1; i <= m; ++i) { 51         int x, y, z; 52         x = read(), y = read(); 53  insert(x, y); 54  insert(y, x); 55  } 56     for(int i = 1; i <= n; ++i) 57         if(!dfn[i]) tarjan(i, 0); 58     for(int i = 2; i < len; i += 2) 59         if(bridge[i]) 60             cout << e[i ^ 1].y << ' ' << e[i].y << '\n'; 61     return 0; 62 }
求橋的板子(參考便可,細節錯誤請無視)

割點的斷定法則

割點的斷定法則
若x不是搜索樹的根節點,則x是割點當且僅當搜索樹上存在x的一個子節點y,知足:
    dfn[x] <= low[y]
特別地,若x是搜索樹的根節點,則x是割點當且僅當搜索樹上存在至少兩個子節點y1, y2知足上述條件

割點斷定的符號爲小於等於號,沒必要再考慮父節點和重邊的問題

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 const int maxn = 100086;  4 struct node {  5     int y, net;  6 }e[maxn << 1];  7 int lin[maxn], len = 0;  8 int n, m, root, num = 0;  9 int dfn[maxn], low[maxn]; 10 bool cut[maxn]; 11 
12 inline int read() { 13     int x = 0, y = 1; 14     char ch = getchar(); 15     while(!isdigit(ch)) { 16         if(ch == '-') y = -1; 17         ch = getchar(); 18  } 19     while(isdigit(ch)) { 20         x = (x << 1) + (x << 3) + ch - '0'; 21         ch = getchar(); 22  } 23     return x * y; 24 } 25 
26 inline void insert(int xx, int yy) { 27     e[++len].y = yy; 28     e[len].net = lin[xx]; 29     lin[xx] = len; 30 } 31 
32 void tarjan(int x) { 33     dfn[x] = low[x] = ++num; 34     int flag = 0; 35     for(int i = lin[x]; i; i = e[i].net) { 36         int to = e[i].y; 37         if(!dfn[to]) { 38  tarjan(to); 39             low[x] = min(low[x], low[to]); 40             if(low[to] >= dfn[x]) { 41                 flag++; 42                 if(x != root || flag > 1) cut[x] = true; 43  } 44  } 45         else low[x] = min(low[x], dfn[to]); 46  } 47 } 48 
49 int main() { 50     n = read(), m = read(); 51     len = 1; 52     for(int i = 1; i <= m; ++i) { 53         int x, y; 54         x = read(), y = read(); 55         if(x == y) continue; 56  insert(x, y); 57  insert(y, x); 58  } 59     for(int i = 1; i <= n; ++i) 60         if(!dfn[i]) { 61             root = i; 62  tarjan(i); 63  } 64     for(int i = 1; i <= n; ++i) 65         if(cut[i]) 66             cout << i << ' '; 67     cout << "are cut-vertexes" << '\n'; 68     return 0; 69 }
割點板子

雙連通份量

若一張無向圖不存在割點,則稱它爲點雙聯通圖。
無向圖的「極大點雙連通子圖」稱爲「點雙連通份量」,簡記爲「v-BCC」。無向連通圖的極大邊雙連通圖的極大邊雙連通子圖,稱爲「邊雙連通份量」,簡記爲「e-DCC」
統稱爲「雙連通份量」,簡記爲「BCC」
定理:
一張無向連通圖是「點雙連通圖」,當且僅當知足下列兩個條件之一:
1.圖的頂點不超過2
2.圖中任意兩點都同時包含在至少一個簡單環中。其中,簡單環指的是不自交的環。

一張無向連通圖是「邊雙連通圖」,當且僅當任意一條邊都包含在至少一個簡單環中。

邊雙連通份量的求法
求出無向圖中全部的橋,將橋刪除後,圖會分紅若干塊,每個連通塊都是一個「邊雙連通份量」

先用tarjan算法標記出全部的橋。而後再對整個無向圖執行一次深度優先遍歷(遍歷的過程當中不訪問),劃分出每一個連通塊。

邊雙連通份量縮點

把每一個e-BCC看做一個節點,把橋邊(x, y)看作鏈接編號爲c[x]和c[y]的e-BCC對應節點的無向邊,會產生一棵樹(若原來的無向圖不連通,則產生森林)。
這種把e-BCC收縮爲一個節點的方法稱爲「縮點」。
把e-BCC縮點,存儲在另外一個鄰接表中

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 const int maxn = 100086;  4 struct shiki {  5     int y, net;  6 }e[maxn << 1], e_BCC[maxn << 1];  7 int n, m, num = 0;  8 int lin[maxn], len = 0;  9 int dfn[maxn], low[maxn]; 10 bool bridge[maxn << 1]; 11 int c_id[maxn], v_bcc = 0; 12 int c_lin[maxn], c_len = 0; 13 
14 inline int read() { 15     int x = 0, y = 1; 16     char ch = getchar(); 17     while(!isdigit(ch)) { 18         if(ch == '-') y = -1; 19         ch = getchar(); 20  } 21     while(isdigit(ch)) { 22         x = (x << 1) + (x << 3) + ch - '0'; 23         ch = getchar(); 24  } 25     return x * y; 26 } 27 
28 inline void insert(int xx, int yy) { 29     e[++len].y = yy; 30     e[len].net = lin[xx]; 31     lin[xx] = len; 32 } 33 
34 inline void t_bridge(int x, int in_edge) { 35     dfn[x] = low[x] = ++num; 36     for(register int i = lin[x]; i; i = e[i].net) { 37         int to = e[i].y; 38         if(!dfn[to]) { 39  t_bridge(to, i); 40             low[x] = min(low[x], low[to]); 41             if(dfn[x] < low[to]) 42                 bridge[i] = bridge[i ^ 1] = 1; 43  } 44         else if(i != (in_edge ^ 1)) 45             low[x] = min(low[x], dfn[to]); 46  } 47 } 48 
49 void dfs(int x) { 50     c_id[x] = v_bcc; 51     for(register int i = lin[x]; i; i = e[i].net) { 52         int to = e[i].y; 53         if(c_id[to] || bridge[i]) continue; 54  dfs(to); 55  } 56 } 57 
58 inline void add_c(int xx, int yy) { 59     e_BCC[++c_len].y = yy; 60     e_BCC[c_len].net = c_lin[xx]; 61     c_lin[xx] = c_len; 62 } 63 
64 int main() { 65     memset(dfn, 0, sizeof(dfn)); 66     n = read(), m = read(); 67     len = 1; 68     for(register int i = 1; i <= m; ++i) { 69         int x, y; 70         x = read(), y = read(); 71  insert(x, y); 72  insert(y, x); 73  } 74     for(register int i = 1; i <= n; ++i) 75         if(!dfn[i]) t_bridge(i, 0); 76     for(register int i = 2; i < len; i += 2) 77         if(bridge[i]) cout << e[i ^ 1].y <<' ' << e[i].y << '\n'; 78     for(register int i = 1; i <= n; ++i) 79         if(!c_id[i]) { 80             ++v_bcc; 81  dfs(i); 82  } 83     for(register int i = 1; i <= n; ++i) 84         cout << i << ' ' << "belong to" << ' ' << c_id[i] << '\n'; 85     for(register int i = 2; i <= len; ++i) { 86         int x = e[i ^ 1].y, y = e[i].y; 87         if(c_id[x] == c_id[y]) continue; 88  add_c(c_id[x], c_id[y]); 89     // add_c(c_id[y], c_id[x]);
90  } 91     printf("縮點後的森林, 點數%d, 邊數%d(可能有重邊)\n", v_bcc, c_len / 2); 92     for(register int i = 2; i <= c_len; ++i) 93         cout << e_BCC[i ^ 1].y << ' ' << e_BCC[i].y << '\n'; 94     return 0; 95 }
橋與邊雙與縮點

點雙連通份量求法

若某個節點是孤立點,則它本身構成一個v-BCC。除了孤立點外,點雙連通份量的大小至少爲2.根據v-DCC定義的極大性,雖然橋不屬於任何e-DCC,可是割點可能屬於多個v-DCC
爲了求出「點雙連通份量」,須要在atjan算法的過程當中維護一個棧,並按照以下方式維護棧中元素:
1.當一個節點第一次被訪問時,把該節點入棧
2.當割點斷定法則中的條件dfn[x] <= low[y]成立時,不管x是否爲根,都須要:
(1)從棧頂不斷彈出節點,直至節點y被彈出
(2)剛纔彈出的全部節點與節點x一塊兒構成一個v-BCC

點雙連通份量縮點

由於一個割點可能屬於多個v-BCC,設圖中有p個割點和t個v-BCC,咱們創建一張包含p+t個節點的新圖,把每一個v-BCC和每一個割點都做爲新圖中的節點,並在每一個割點與包含它的全部v-BCC之間連邊。

這張圖就變成了一棵樹或是一片森林

 1 //尚缺縮點 
 2 #include<bits/stdc++.h>
 3 #define uint unsigned int
 4 using namespace std;  5 const int maxn = 100086;  6 struct shiki {  7     int y, net;  8 }e[maxn << 1], vbcc[maxn];  9 int n, m, root, num = 0;  10 int lin[maxn], len = 0, cnt = 0;  11 int dfn[maxn], low[maxn];  12 bool cut[maxn];  13 int s[maxn], top = 0;  14 int new_id[maxn];  15 int v_lin[maxn], v_len = 0;  16 vector<int> dcc[maxn];  17 int c[maxn];  18 
 19 inline int read() {  20     int x = 0, y = 1;  21     char ch = getchar();  22     while(!isdigit(ch)) {  23         if(ch == '-') y = -1;  24         ch = getchar();  25  }  26     while(isdigit(ch)) {  27         x = (x << 1) + (x << 3) + ch - '0';  28         ch = getchar();  29  }  30     return x * y;  31 }  32 
 33 inline void insert(int xx, int yy) {  34     e[++len].y = yy;  35     e[len].net = lin[xx];  36     lin[xx] = len;  37 }  38 
 39 void t_vertexes(int x) {  40     dfn[x] = low[x] = ++num;  41     s[++top] = x;  42     if(x == root && lin[x] == 0) {  43         dcc[++cnt].push_back(x);  44         return;  45  }  46     int flag = 0;  47     for(register int i = lin[x]; i; i = e[i].net) {  48         int to = e[i].y;  49         if(!dfn[to]) {  50  t_vertexes(to);  51             low[x] = min(low[x], low[to]);  52             if(dfn[x] <= low[to]) {  53                 flag++;  54                 if(x != root || flag > 1) cut[x] = 1;  55                 cnt++;  56                 int z;  57                 do {  58                     z = s[top--];  59  dcc[cnt].push_back(z);  60                 }while(z != to);  61  dcc[cnt].push_back(x);  62  }  63  }  64         else low[x] = min(low[x], dfn[to]);  65  }  66 }  67 
 68 inline void add_c(int xx, int yy) {  69     vbcc[++v_len].y = yy;  70     vbcc[v_len].net = v_lin[xx];  71     v_lin[xx] = v_len;  72 }  73 
 74 int main() {  75     n = read(), m = read();  76     for(register uint i = 1; i <= m; ++i) {  77         int x, y;  78         x = read(), y = read();  79  insert(x, y);  80  insert(y, x);  81  }  82     for(register uint i = 1; i <= n; ++i)  83         if(!dfn[i]) {  84             root = i;  85  t_vertexes(i);  86  }  87     for(register uint i = 1; i <= n; ++i)  88         if(cut[i]) cout << i << ' ';  89     cout << "are cut-vertexes" << '\n';  90     for(int i = 1; i <= cnt; ++i) {  91         printf("e-DCC #%d:", i);  92         for(int j = 0; j < dcc[i].size(); ++j)  93             printf(" %d", dcc[i][j]);  94         cout << '\n';  95  }  96     num = cnt;  97     for(register int i = 1; i <= n; ++i)  98         if(cut[i]) new_id[i] = ++num;//給割點以新的編號 
 99     v_len = 1; 100     for(register int i = 1; i <= cnt; ++i) 101         for(register int j = 0; j < dcc[i].size(); ++j) { 102             int x = dcc[i][j]; 103             if(cut[x]) { 104  add_c(i, new_id[x]); 105  add_c(new_id[x], i); 106  } 107             else c[x] = i;//除割點外,其它點僅屬於1個v-bcc 
108  } 109     printf("縮點以後的森林, 點數%d, 邊數%d\n", num, v_len / 2); 110     printf("編號1~%d的爲原圖v-BCC, 編號>%d的原圖割點\n", cnt, cnt); 111     for(register int i = 2; i < v_len; i += 2) 112         printf("%d %d\n", vbcc[i ^ 1], vbcc[i]); 113     return 0; 114 }
割點與點雙與縮點

例題1:BLO(Bzoj1123)

 

對於詢問的節點i,可能有兩種狀況
1.i不是割點
2.i是割點

若i不是割點,則將i以及與i有直接相連的邊刪去後,圖分爲了i和其餘節點這個兩部分
即:i被孤立出來了
此時對於不能與i聯通的點的個數爲n-1,即有n-1個點不能與i相互到達。
由於題目求的是有序點對,也就是說,例如(1, 2)和(2, 1),這是不一樣的點對。
因此若i不是割點,則答案爲2*(n-1)

若i是割點,則刪去i以及全部與i相連的邊後,圖會分紅若干個連通塊。
最後的答案很顯然,咱們應該求出這些連通塊的大小,兩兩相乘再相加
在圖的連通性問題中,咱們常常要從搜索樹的角度來進行分析。
設在搜索樹上,節點i的子節點集合中,有t割點s1,s2,s3...st知足割點斷定法則dfn[i] <= low[sk]。因而刪除i關聯的全部邊後無向圖至多分紅t+2個連通塊
每一個連通塊的節點構成狀況爲:
1.節點i自身單獨構成一個連通塊
2.有t個連通塊,分別由搜索樹上以sk(1 <= k <= t)爲根的子樹中的節點構成
3.還可能有一個連通塊,由除了上述節點之外的全部點構成。
(第三點,即雖然與i相連通,但i不做爲搜索樹的根。由於整個圖是連通的,在不刪掉任何一個點是,搜索樹只有一個點爲根,刪掉與i直接相連的邊,則被分開的是i,
i的子樹和i的父親所在了連通塊)

 

 

那麼能夠在tarjan算法執行深度優先遍歷的過程正,順便求出搜索樹每棵「子樹」的大小,設size[x]表示已x爲根的子樹的大小。
綜上所述,刪掉一個割點i以後,不連通的有序對數量爲:
設sum = size[s1]+size[s2]+....+size[t-1]+size[t]
size[s1]*(n-size[s1])+size[s2]*(n-size[s2])+...+size[st]*(n-size[st])+1*(n-1)+(n-1-sum)*(1+sum)

 

 1 #include<iostream>
 2 #include<iomanip>
 3 #include<ctime>
 4 #include<cstring>
 5 #include<cmath>
 6 #include<cstdio>
 7 #include<cstdlib>
 8 #include<algorithm>
 9 #include<queue>
10 #include<vector>
11 #include<map>
12 #include<stack>
13 #define ll long long
14 #define uint unsigned int
15 using namespace std; 16 const int maxn = 100010, maxm = 500010; 17 struct shiki { 18     int y, net; 19 }e[maxm << 1]; 20 int lin[maxn], len = 0; 21 int n, m, num = 0; 22 int size[maxn]; 23 ll ans[maxn]; 24 int dfn[maxn], low[maxn]; 25 bool cut[maxn]; 26 
27 inline int read() { 28     int x = 0, y = 1; 29     char ch = getchar(); 30     while(!isdigit(ch)) { 31         if(ch == '-') y = -1; 32         ch = getchar(); 33  } 34     while(isdigit(ch)) { 35         x = (x << 1) + (x << 3) + ch - '0'; 36         ch = getchar(); 37  } 38     return x * y; 39 } 40 
41 inline void insert(int xx, int yy) { 42     e[++len].y = yy; 43     e[len].net = lin[xx]; 44     lin[xx] = len; 45 } 46 
47 void tarjan(int x) { 48     dfn[x] = low[x] = ++num, size[x] = 1; 49     int flag = 0, sum = 0; 50     for(register int i = lin[x]; i; i = e[i].net) { 51         int to = e[i].y; 52         if(!dfn[to]) { 53  tarjan(to); 54             size[x] += size[to]; 55             low[x] = min(low[x], low[to]); 56             if(dfn[x] <= low[to]) { 57                 flag++; 58                 ans[x] += 1ll * size[to] * (n - size[to]); 59                 sum += size[to]; 60                 if(x != 1 || flag > 1) cut[x] = 1; 61  } 62  } 63         else low[x] = min(low[x], dfn[to]); 64  } 65     if(cut[x]) ans[x] += 1ll * (n - sum - 1) * (sum + 1) + (n - 1); 66     else ans[x] = 2 * (n - 1); 67 } 68 
69 int main() { 70     n = read(), m = read(); 71     for(register int i = 1; i <= m; ++i) { 72         int x, y; 73         x = read(), y = read(); 74         if(x == y) continue; 75  insert(x, y); 76  insert(y, x); 77  } 78     tarjan(1);//由於已經確保圖是連通的 ,因此能夠直接計算 
79     for(register int i = 1; i <= n; ++i) 80         printf("%lld\n", ans[i]); 81     return 0; 82 }

例題2:Network(poj3694)

給定一張N個點M條邊的無向連通圖,而後執行Q次操做,每次想圖中添加一條邊,而且詢問當前無向圖中「橋」的數量(N <= 10^5, m <= 2*10^5, Q<=1000)

先利用tarjan算法求出無向圖中全部的邊雙連通份量,並對全部的邊雙連通份量執行縮點操做。

這樣就造成了一顆樹,這個樹上的邊就是最初「橋」的個數

思考加入新的邊(x, y)

對於x和y兩個點

1.若兩個點在同一個e-BCC中,則在x和y之間連邊不會影響最終橋的數量

2.若x,y屬於不一樣的e-BCC,則在縮點以後獲得的樹上,x所在的邊雙和y所在的邊雙之間的路徑都不在是橋,由於將x,y連邊後,他們都處在一個環中。

咱們能夠求出P = LCA(c_id[x], c_id[y]),即:x所在的邊雙與y所在的邊雙的最近公共祖先,同時把路徑上的全部邊都標記爲「不是橋」。

由於數據不算大,因此咱們就能夠AC了

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<vector>
 6 #include<queue>
 7 #include<cstdlib>
 8 #include<ctime>
 9 #include<iomanip>
 10 #include<cmath>
 11 using namespace std;  12 const int maxm = 500010, maxn = 100010;  13 struct shiki {  14     int y, net;  15 }e[maxm];  16 int low[maxn], dfn[maxn], num = 0, deep[maxn];  17 int len, n, m, lin[maxn];  18 int flag, bridge[maxn], father[maxn];  19 
 20 inline void init() {  21     len = 0, num = 0, flag = 0;  22     memset(dfn, 0, sizeof(dfn));  23     memset(deep, 0, sizeof(deep));  24     memset(bridge, 0, sizeof(bridge));  25     memset(lin, 0, sizeof(lin));  26     memset(low, 0, sizeof(low));  27     memset(father, 0, sizeof(father));  28     for(int i = 1; i <= n; i++) father[i] = i;  29 }  30 
 31 inline void insert(int xx, int yy) {  32     e[++len].y = yy;  33     e[len].net = lin[xx];  34     lin[xx] = len;  35 }  36 
 37 void tarjan(int x) {  38     dfn[x] = low[x] = ++num;  39     deep[x] = deep[father[x]] + 1;  40     for(int i = lin[x]; i; i = e[i].net) {  41         int to = e[i].y;  42         if(!dfn[to]) {  43             father[to] = x;  44  tarjan(to);  45             low[x] = min(low[x], low[to]);  46             if(low[to] > dfn[x]) {  47                 flag++;  48                 bridge[to] = 1;  49  }  50  }  51         else if(to != father[x]) low[x] = min(low[x], dfn[to]);  52  }  53  
 54 }  55 
 56 void LCA(int u, int v) {  57     while(deep[u] > deep[v]) {  58         if(bridge[u])  flag--, bridge[u] = 0;  59         u = father[u];  60  }  61     while(deep[v] > deep[u]) {  62         if(bridge[v]) flag--, bridge[v] = 0;  63         v = father[v];  64  }  65     while(u != v) {  66         if(bridge[u]) flag--, bridge[u] = 0;  67         if(bridge[v]) flag--, bridge[v] = 0;  68         u = father[u];  69         v = father[v];  70  }  71 }  72 void ask()  73 {  74     int q, u, v;  75     scanf("%d", &q);  76     for(int i = 1; i <= q; ++i) {  77         scanf("%d%d", &u, &v);  78  LCA(u, v);  79         printf("%d\n", flag);  80  }  81     printf("\n");  82 }  83 int main()  84 {  85     int cas = 0;  86     while(scanf("%d%d", &n, &m) != EOF) {  87         if(n == 0 && m == 0) break;  88         printf("Case %d:\n", ++cas);  89  init();  90         int x, y;  91         while(m--) {  92             scanf("%d%d", &x, &y);  93  insert(x, y);  94  insert(y, x);  95  }  96         tarjan(1);  97  ask();  98  }  99     return 0; 100 }
相關文章
相關標籤/搜索