並查集在實際問題中的應用

Absoler發表於2018-08-04

並查集:用以將元素高效分組以及區分。

題目來源:codeforces 1012B

原問題如下,在一個(n*m)的table上,element會做出一種增值行為,如果有三個物質處於某個矩形的三個頂點上,那麼在第四個頂點上會自動增值出一個element。現在table上已經存在了一些物質,求出最少仍需多少額外的物質,使得其可以將table覆蓋。

對於解這道題來說,靈活的轉化思想非常重要。首先我們可以證明,如果一個矩形有兩條相鄰的邊被覆蓋滿,這個矩形就可以通過增值自行覆蓋。進一步我們可以發現,如果將這兩條邊按垂直方向“打散”之後分散在矩形中,依然可以完成增值。

這個性質的本質是什麼呢,可以將存在三個點(r1,c1),(r1,c2),(r2,c1),看作在(r1,c1),(r1,c2),(r2,c1)三對值之間存在聯絡,因而r2和c2也發生了聯絡,於是點(r2,c2)也隨之存在,因此可以用並查集來解決問題,全集即為(r1,r2……rn,c1,c2……cn)。

每輸入一個點,即將對應兩個值unite起來;輸入完畢後,會出現若干集合。這些集合內部的橫縱座標值自由組合形成的點就是已存在的;而集合之間所能組合形成的點都是尚未塗色的。最終我們希望達到的效果是,所有值都處在一個集合裡,這樣不論考查哪個點,它的橫縱座標一定是聯絡起來的。因此我們額外塗點的作用實際上,是將不同集合聯絡起來,因此結果等於集合數-1

附AC程式碼

 

#include<iostream>
#include<cstring>
using namespace std;

int p[400010],r[400010];

int fa(int ch)
{
    if(p[ch]==ch)
        return ch;
    return fa(p[ch]);
}

void unite(int u,int v)
{
    int fu=fa(u);
    int fv=fa(v);
    if(fu!=fv)
    {
        if(r[fv]==r[fu])
        {
            r[fu]++;
            p[fv]=fu;
        }
        else if(r[fu]<r[fv])
            p[fu]=fv;
        else
            p[fv]=fu;
    }
}

int main()
{
    int n,m,q;
    cin>>n>>m>>q;
    for(int i=1; i<=n+m; i++)
        p[i]=i;
    for(int i=0; i<q; i++)
    {
        int r,c;
        scanf("%d%d",&r,&c);
        unite(r,c+n);
    }
    int ans=0;
    for(int i=1; i<=n+m; i++)
        if(p[i]==i)
            ans++;
    cout<<ans-1<<endl;
    return 0;
}

 

相關文章