顧名思義,就是在維護集合關係的樹中添加邊權的並查集,這樣作能夠維護更多的信息。c++
引入題目:https://www.luogu.com.cn/problem/P2024
好比這道題,若是使用普通的並查集則沒法處理,由於普通的並查集只可以刻畫兩個物品是否屬於同一個集合。所以這時候就要使用可以記錄更多信息的帶權並查集。spa
在閱讀前,須要先掌握並查集的知識。code
結合題目講解:https://www.luogu.com.cn/problem/P2024
blog
對於一個物種(一類動物),若是存在它吃另外一個物種的關係,則讓它的度數比另外一個物種多 \(1\) 。更嚴格地說,咱們記該物種爲 a
(並不是題意中的A
),另外一個物種是 b
,它們對應的度數爲d[]
,那麼有 \(d[a]=d[b]+1\) 。如圖:
遞歸
那麼有了這樣的規定,便有以下性質:ci
d[a]%3==d[b]%3
時,a
,b
是同一個物種。(操做1)((d[a]-d[b])%3+3)%3==1
時,存在a
吃b
的關係。(這裏屢次取模是爲了保證左邊的值只可能爲 \(0,1,2\) )(操做2)從上面的性質能夠看出,兩個物種的關係與它們的模數(這題是 \(mod3\) )餘多少關係密切相關,所以接下來咱們也會着重考察兩個數之間的這種關係。get
利用度數以及並查集,便可將各類動物之間的關係刻畫清楚:string
這裏依然對a
,b
進行討論,爲了方便,咱們記a
的祖宗(根節點)爲pa
,b
的祖宗(根節點)爲pb
。
it
若pa
,pb
不在同一個集合中:
就進行並查集的合併操做,讓f[pa]=pb
。能夠看出,在合併的時候,仍然做爲根節點的pb
的度數仍是 \(0\),可是pa
的度數須要做出調整,纔可以保證結點之間關係的正確。
① 若是a
和b
是同一個物種(操做1):則有 d[pa]+d[a]=d[b]
② 若是a
吃b
(操做2):則有 d[pa]+d[a]-d[b]=1
(固然,右式等於 \(4,7,10\) 這樣的數也是能夠的,咱們只需找到 \(mod 3餘1\)的數 )
class
若pa
,pb
在同一個集合中:
相似於上面的討論,
① 若是a
和b
是同一個物種(操做1):若是 ((d[a]-d[b])%3+3)%3!=0
,則矛盾,這句話即是謊話。
② 若是a
吃b
(操做2):若是 ((d[a]-d[b])%3+3)%3!=1
,則矛盾,這句話即是謊話。
綜上,咱們的討論將全部狀況覆蓋了。
路徑壓縮:
根據並查集的性質,若是不進行路徑壓縮,時間複雜度將會退化到 \(O(N)\) 。所以帶權並查集也要進行路徑壓縮,那麼主要問題就是解決如何維護d[]
(度數)的問題:
歸納地說,就是在查詢到某個點的時候,在搜索它的祖宗時遞歸地求出路上全部結點的度數,那麼它的度數就是d[x]+=d[f[x]]
。
如上圖,pa
在一次操做中併入了pb
。
而在另外一次操做中,對a
的進行了查詢(求祖宗),便有以下路徑壓縮的並更新d[]
的過程:
遞歸地找出祖宗pb
。
pa
的祖宗就是pb
,度數在合併的時候已經求出來了,因此更新 \(0\)。
c
的父親節點是pa
,合併的時候並無更新(所以記錄的是距離pa
的度數),度數須要加上 \(d[pa]\),而後進行路徑壓縮。
a
的父親節點是c
,在上一步更新了,因此度數加上 \(d[c]\) 便可,相似的,進行路徑壓縮。
(這裏可能有點難理解,不過只要記住:所謂的d[x]
指的是節點x
相對於它父節點的度數便可)
不理解的地方能夠結合代碼理解
放上代碼:(很短的)
#include<bits/stdc++.h> using namespace std; const int N=5e4+5; int f[N],d[N]; int find(int x){ if(x!=f[x]){ int root=find(f[x]); d[x]+=d[f[x]]; f[x]=root; } return f[x]; } int main(){ int n,m; cin>>n>>m; for(int i=1;i<N;i++) f[i]=i; int cnt=0; while(m--){ int op,a,b; cin>>op>>a>>b; //2,3 judge if(a>n || b>n){ cnt++; continue; } if(a==b && op==2){ cnt++; continue; } int pa=find(a),pb=find(b); int t= op==2; if(pa==pb){ if(((d[a]-d[b])%3+3)%3!=t) cnt++; }else{ f[pa]=pb; d[pa]=t+d[b]-d[a]; } } cout<<cnt<<endl; return 0; }
https://www.acwing.com/problem/content/241/
分析:思路徹底相似於食物鏈那題。
const int N=2e4+5;
unordered_map<int,int> h;
int n,m;
int f[N];
int d[N];
int get(int x){
if(h.count(x)==0) h[x]=++n;
return h[x];
}
int find(int x){
if(f[x]!=x){
int root=find(f[x]);
d[x]^=d[f[x]];
f[x]=root;
}
return f[x];
}
int main(){
cin>>n>>m;
n=0;
//init for(int i=1;i<N;i++) f[i]=i; int ans=m; for(int i=1;i<=m;i++){ int a,b; string op; cin>>a>>b>>op; a=get(a-1); b=get(b); int t= op=="odd"; int pa=find(a),pb=find(b); if(pa==pb){ if(abs(d[a]-d[b])!=t){ ans=i-1; break; } }else{ //merge f[pa]=pb; d[pa]=d[a]^d[b]^t; } } cout<<ans<<endl; return 0;
}