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


基本概念

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

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

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

追溯值
追溯值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])

橋的判定法則

無向邊(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)也就不是橋

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

需要注意的是, 因為我們要遍歷的是無向圖, 所以從每個節點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]即可

(補充:^的成對變換

對於非負整數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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM