POJ - 3694 - Network
給定一張N個點M條邊的無向連通圖,然後執行Q次操作,每次向圖中添加一條邊,並且詢問當前無向圖中“橋”的數量。。
首先運行一次tarjan,求出橋和縮點,那麼無向圖縮點爲一棵樹,樹邊正好是原來的橋。每次操作連接兩點,看看這兩點是不是在同一個縮點內,如果是,那麼縮點後的樹沒任何變化,如果兩點屬於不同的縮點,那麼連接起來,然後找這兩個縮點的LCA,,因爲從點u到LCA再到點v再到點u,將形成環,裏面的樹邊都會變成不是橋。計數的時候注意,有些樹邊可能之前已經被標記了,這次再經過不能再標記
利用並查集優化,當樹上的邊不再是橋了以後,就把這條邊的子結點所在的集合合併到父節點所在的集合上,這樣從走到時,每一步可以直接走到並查集Get返回的結點上,利用並查集對不再是橋的邊進行路徑壓縮,這樣將時間複雜度從原來的優化到
(第一次走的時候沒有任何影響,但是再往後的數據,再次走到了以後會直接把前面走過的不用走的路全部跳過去,達到優化的目的)
AC代碼:
//這道題代碼雖然比較長,但是每個函數各司其職,井井有條,寫起來非常舒服
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>
#define ls (p<<1)
#define rs (p<<1|1)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 2e5+7;
const int M = 5e5+7;
int head[N],nex[M],ver[M],tot = 1;//原圖
int hc[N],nc[M],vc[M],tc = 1;//縮點後建圖
int dfn[N],low[N],num;//tarjan專用
int dcc,cnt;//強連通專用
int n,m,t,T;//輸入專用
bool bridge[M];//割邊專用
int deep[N],f[N][20];//LCA專用
int c[N];//縮點專用
int fa[N];//並查集專用
void add(int x,int y){
ver[++tot] = y;nex[tot] = head[x];head[x] = tot;
}
void add_c(int x,int y){
vc[++tc] = y;nc[tc] = hc[x];hc[x] = tc;
}
void tarjan(int x,int in_edge){
dfn[x] = low[x] = ++num;
for(int i = head[x];i;i = nex[i]){
int y = ver[i];
if(!dfn[y]){
tarjan(y,i);
low[x] = min(low[x],low[y]);
if(low[y] > dfn[x])
bridge[i] = bridge[i ^ 1] = true;//成對變換
}
else if(i != (in_edge ^ 1))//i和in_edge都是前向星的指針編號
low[x] = min(low[x],dfn[y]);
}
}
void dfs(int x){
c[x] = dcc;
for(int i = head[x];i;i = nex[i]){
int y = ver[i];
if(c[y] || bridge[i])continue;
dfs(y);
}
}
queue<int>q;
void bfs(){//求的是縮點後的圖
deep[1] = 1;//根節點的深度是1
q.push(1);
while(q.size()){
int x = q.front();
q.pop();
for(int i = hc[x];i;i = nc[i]){
int y = vc[i];
if(deep[y])continue;
deep[y] = deep[x] + 1;
f[y][0] = x;
over(j,1,19)
f[y][j] = f[f[y][j - 1]][j - 1];
q.push(y);
}
}
}
int lca(int x,int y){
if(deep[x] > deep[y])swap(x,y);
lver(i,18,0)
if(deep[f[y][i]] >= deep[x])
y = f[y][i];
if(x == y)return x;
lver(i,18,0)
if(f[x][i] != f[y][i])
x = f[x][i],y = f[y][i];
return f[x][0];
}
int Get(int x){
if(x == fa[x])return x;
return fa[x] = Get(fa[x]);
}
int main()
{
while(scanf("%d%d",&n,&m) != EOF && n){
tot = 1;dcc = num = 0;
over(i,1,n)
head[i] = hc[i] = dfn[i] = low[i] = deep[i] = c[i] = 0;
over(i,1,2 * m + 1)
bridge[i] = 0;
over(i,1,m){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
over(i,1,n)
if(!dfn[i])
tarjan(i,0);
//兩個套路都一樣
over(i,1,n)
if(!c[i])
++dcc,dfs(i);
//縮完點該建圖了
tc = 1;
over(i,2,tot){//建圖就用到了成對變換
int x = ver[i ^ 1],y = ver[i];
if(c[x] == c[y])continue;
add_c(c[x],c[y]);
//因爲之前是無向圖轉有向圖是雙邊,這裏已經是有向圖了只需要建單邊即可
}
//lca預處理
bfs();
//並查集初始化
over(i,1,dcc)
fa[i] = i;
scanf("%d",&t);
int ans = dcc - 1;//dcc是縮點後的點數,而ans是邊數,所以最開始ans,樹的邊數等於點數 - 1
printf("Case %d:\n", ++T);
while(t--){
int x,y;
scanf("%d%d",&x,&y);
x = c[x],y = c[y];
int p = lca(x,y);
x = Get(x);
while(deep[x] > deep[p]){
fa[x] = f[x][0];//先給fa賦值
ans--;
x = Get(x);//這樣get的時候x也能往上走
}
y = Get(y);
while(deep[y] > deep[p]){
fa[y] = f[y][0];
ans--;
y = Get(y);
}
printf("%d\n",ans);
}
cout<<endl;
}
return 0;
}