並查集在實際問題中的應用
阿新 • • 發佈:2019-01-02
並查集:用以將元素高效分組以及區分。
題目來源: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; }