1. 程式人生 > 其它 >並查集(擴充套件)

並查集(擴充套件)

前言

本文作為 並查集 的補充說明和擴充套件。

正文

我們在做並查集時,\(f_i\) 一般表達的是親戚關係,但在這道題 P1892 中有兩種關係,一種為親戚關係,另一種為敵對關係,這就涉及到了另一個知識:反集。\(i\) 的反集一般用 \(f_{i+n}\) 來表示,其他的基本與並查集無異(但是在join(i+n,j)中,兩個引數千萬不能寫反),上程式碼(P1892):

#include <iostream>

using namespace std;
int n,m,ans;
char c;
int f[2001];

int find(int x) {
    if (f[x]==x) {
        return x;
    }
    return f[x]=find(f[x]);
}

void join(int x,int y) {
    int nx=find(x),ny=find(y);
    if (nx!=ny) {
        f[nx]=ny;
    }
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
#endif
    cin>>n>>m;
    for (int i=1;i<=2*n;i++) {
        f[i]=i;
    }
    for (int i=1;i<=m;i++) {
        cin>>c;
        int t1,t2;
        cin>>t1>>t2;
        if (c=='F') {
            join(t1,t2);
        } else if (c=='E') {
            join(t1+n,t2);
            join(t2+n,t1);
        }
    }
    for (int i=1;i<=n;i++) {
        if (f[i]==i) {
            ans++;
        }
    }
    cout<<ans;
}

可以發現基本與並查集無異,只是需要多寫一個join

如有不懂還可參考 there

再來看一道題 P2024 ,第一眼看上去就覺得是一個反集,於是便自信的打了一個反集上去,然後,成功地連樣例都沒過。

………

這是為啥,仔細讀題你會發現,題目中的關係為:\(A\)\(B\)\(B\)\(C\)\(C\)\(A\),已就是說 \(A\)\(B\)\(B\) 不吃 \(A\),所以反集不成立(因為這是單向關係),所以我們就素要把 \(f\) 陣列再開一倍(也就是開三倍)去儲存關係,即 \(f_i\) 表示為 \(i\) 的親戚,\(f_{i+n}\) 表示為 \(i\)

的獵物,\(f_{i+n+n}\) 表示為 \(i\) 的天敵,再使用並查集去套即可AC,上程式碼:

#include <iostream>
#include <cstdio>

using namespace std;
int f[300005];
int n,k,ans;

inline int read() {//要寫快讀,cin過不去
    int sum=0;
    char ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch>='0'&&ch<='9') sum=sum*10+ch-48,ch=getchar();
    return sum;
}

int find(int x) {
    if (x==f[x]) {
        return x;
    }
    return f[x]=find(f[x]);
}

void join(int x,int y) {
    int nx=find(x),ny=find(y);
    f[nx]=ny;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
#endif
    int x,y,z;
    n=read(),k=read();
    // cin>>n>>k;
    for (int i=1;i<=3*n;++i)  {
        f[i]=i;
    }
    for(int i=1;i<=k;++i) 
    {
        z=read(),x=read(),y=read();
        // cin>>z>>x>>y;
        if (x>n or y>n) {
            ans++;
            continue;
        }
        if(z==1)
        {
            if(find(x+n)==find(y) or find(x+n+n)==find(y)) {
                ans++;
            } else {
                join(x,y);
                join(x+n,y+n);
                join(x+n+n,y+n+n);
            }
        } else if (z==2) {
            if (x==y) {
                ans++;
            } else if (find(x)==find(y) or find(x+2*n)==find(y)) {
                ans++;
            } else {
                join(x,y+n+n);
                join(x+n,y);
                join(x+n+n,y+n);
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

並查集的擴充套件應用實際就是開多倍的 \(f\) 陣列去表達關係,並沒有什麼難處。

\(finally\)

最後,以上操作均可使用帶權並查集實現(只不過我不會,逃)。