【割邊縮點】解題報告:POJ - 3694 - Network(Tarjan割邊縮點 + LCA + 並查集優化)

POJ - 3694 - Network

給定一張N個點M條邊的無向連通圖,然後執行Q次操作,每次向圖中添加一條邊,並且詢問當前無向圖中“橋”的數量。N105,M2105,Q1000N≤10^5,M≤2*10^5,Q≤1000

首先運行一次tarjan,求出橋和縮點,那麼無向圖縮點爲一棵樹,樹邊正好是原來的橋。每次操作連接兩點,看看這兩點是不是在同一個縮點內,如果是,那麼縮點後的樹沒任何變化,如果兩點屬於不同的縮點,那麼連接起來,然後找這兩個縮點的LCA,,因爲從點u到LCA再到點v再到點u,將形成環,裏面的樹邊都會變成不是橋。計數的時候注意,有些樹邊可能之前已經被標記了,這次再經過不能再標記
利用並查集優化,當樹上的邊不再是橋了以後,就把這條邊的子結點所在的集合合併到父節點所在的集合上,這樣從c[x]c[x]走到LCA(c[x],c[y])LCA(c[x],c[y])時,每一步可以直接走到並查集Get返回的結點上,利用並查集對不再是橋的邊進行路徑壓縮,這樣將時間複雜度從原來的O(M+QN)O(M+Q*N)優化到O(M+QlogN)O(M+QlogN)
(第一次走的時候沒有任何影響,但是再往後的數據,再次走到了以後會直接把前面走過的不用走的路全部跳過去,達到優化的目的)
 
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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章