1. 程式人生 > >【hdu】5354 Bipartite Graph【cdq分治+並查集】

【hdu】5354 Bipartite Graph【cdq分治+並查集】

【題意】:給你一個圖,問刪除一個節點後,該圖還是不是二分圖

【題解】:題意很簡單,顯然資料範圍不可能允許我們n方暴力。關於判斷二分圖我們可以用染色法,也可以使用並查集,染色法每次都需要遍歷全圖,顯然是行不通的,我們這題採用的是cdq分治+並查集,這裡的並查集要求不能路徑壓縮,並且可刪除,關於並查集的可刪除我們可以每次用一個棧記錄新合併之前的節點的狀態,還原的時候把棧中儲存的資訊一個一個拿出來就好。

主要考慮cdq分治對這題目的優化,我們考慮計算刪除a節點之後和刪除b節點之後,顯然不考慮特殊情況,在這兩張圖棧中,絕大多數的邊都還是一樣的,只有連線a和b的少部分邊是不同的,所以在這道題目中我們可以用cdq分治來優化並查集的合併過程(二分圖染色過程).我們考慮solve(l,r),我們把這個分為兩個過程solve(l,mid)solve(mid+1,r),這兩個過程是類似的,並且這裡其實並沒有合併答案的操作。

所以我們就拿solve(l,mid)來說,顯然我們把solve(l,mid)公共需要加上的邊加上就能進行優化,這時我們把所有兩端點都不在[l,mid]區間內的邊都進行合併操作,顯然無論刪除[l,mid]中的哪個節點,這樣的合併操作都不會有什麼問題,所以我們一直重複這樣的操作直到l==r,這樣我們就可以在合適的複雜度解決該問題了

細節見程式碼,

#include<set>
#include<map>
#include<cmath>
#include<stack>
#include<queue>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define PB push_back
#define MP make_pair
#define ll __int64
#define MS(a,b) memset(a,b,sizeof(a))
#define LL (rt<<1)
#define RR (rt<<1|1)
#define lson l,mid,LL
#define rson mid+1,r,RR
#define pii pair<int,int>
#define lowbit (x&(-x))
using namespace std;
const int MAXN=1e5+10;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
vector<int>G[MAXN];
int fa[MAXN],col[MAXN],rk[MAXN],ans[MAXN];
int tot,top;
struct node
{
    int u,v,colu,colv,rku,rkv,fau,fav;
    node(){}
    node(int _u,int _v,int _colu,int _colv,int _rku,int _rkv,int _fau,int _fav):u(_u),v(_v),colu(_colu),colv(_colv),rku(_rku),rkv(_rkv),fau(_fau),fav(_fav){}
}S[MAXN];
void init(int n)
{
    for(int i=1;i<=n;i++)G[i].clear();
    tot=top=0;
    for(int i=0;i<n+10;i++)fa[i]=i,col[i]=1,rk[i]=1;
}
int find_fa(int x)
{
    int o=x;
    while(fa[o]!=o)o=fa[o];
    return o;
}
int find_col(int x)
{
    if(fa[x]==x)return col[x];
    if(!col[x]) return !find_col(fa[x]);
    return find_col(fa[x]);
}
bool merge(int u,int v)
{
    int a=find_fa(u),b=find_fa(v);//ab的根
    int x=find_col(u),y=find_col(v);//uv的顏色
    int root,next;
    if(a==b){//如果同根
        if(x==y)return false;//同色 他們之間又有現在這條邊 說明改圖不是二分圖
        return true;
    }
    if(rk[a]>rk[b])root=a,next=b;//並查集優化操作
    else  root=b,next=a;
    S[top++]=node(a,b,col[a],col[b],rk[a],rk[b],fa[a],fa[b]);//將當前資訊存入棧中 以便還原並查集
    if(x==y&&col[root]==1)col[next]^=1;//如果uv根節點顏色相同,為了合併 將其中一個改變顏色 
    fa[next]=root;
    rk[root]+=rk[next];
    return true;//成功合併,當前並查集集合中的節點構成的是一個二分圖
}
bool unite(int l,int r,int a,int b)
{
    bool flag=true;
    for(int u=l,v;u<=r;u++){
        for(int i=0;i<G[u].size();i++){
            v=G[u][i];
            if(a<=v&&v<=b)continue;//如果v在對立區間中跳過
            if(!merge(u,v))flag=false;//如果merge不成功 
        }
    }
    return flag;
}
void back(int x)
{
    node tmp;
    while(top>x){
        --top;
        tmp=S[top];
        int u=tmp.u,v=tmp.v;
        rk[u]=tmp.rku;rk[v]=tmp.rkv;
        fa[u]=tmp.fau;fa[v]=tmp.fav;
        col[u]=tmp.colu;col[v]=tmp.colv;
    }
}
void cdq(int l,int r,bool flag)
{
    if(l==r){ans[l]=flag;return;}//根據之前的操作這裡已經把所有邊都加上了 除了和l有關的邊 也就相當於刪除了l這個節點,在之前的合併操作過程中就是一個判斷二分圖的過程
    int mid=(l+r)>>1;
    int pre=top;
    bool now=flag&&unite(mid+1,r,l,mid);//把端點不跨越[l,mid] [mid+1,r]兩個區間的邊都連上 和之前操作合併起來就是把所有無關區間[l,mid]中節點的邊都連上
    cdq(l,mid,now);
    back(pre);//刪除之前的並查集操作
    now=flag&&unite(l,mid,mid+1,r);//這裡和上面一樣
    cdq(mid+1,r,now);
    back(pre);
}
int main()
{
    int T,n,m;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        init(n);//初始化
        for(int i=0,u,v;i<m;i++){//建圖
            scanf("%d%d",&u,&v);
            G[u].PB(v);G[v].PB(u);
        }
        cdq(1,n,true);//運算
        for(int i=1;i<=n;i++)printf("%d",ans[i]); puts("");//輸出答案
    }
    return 0;
}