雙連通 縮點 橋 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 400005
struct 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;
}