【資料結構】帶權並查集
阿新 • • 發佈:2021-02-18
# 目錄
+ 簡介
+ 詳細介紹
+ 例題
## 簡介
顧名思義,就是在維護集合關係的樹中新增邊權的並查集,這樣做可以維護更多的資訊。
引入題目:https://www.luogu.com.cn/problem/P2024
比如這道題,如果使用普通的並查集則無法處理,因為普通的並查集只能夠刻畫兩個物品是否屬於同一個集合。因此這時候就要使用能夠記錄更多資訊的**帶權並查集**。
> 在閱讀前,需要先掌握**並查集**的知識。
## 詳細介紹
結合題目講解:https://www.luogu.com.cn/problem/P2024
![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217222142134-320848444.png)
對於一個物種(一類動物),如果存在它吃另一個物種的關係,則讓它的度數比另一個物種多 $1$ 。更嚴格地說,我們記該物種為 `a` (並非題意中的`A`),另一個物種是 `b`,它們對應的度數為`d[]`,那麼有 $d[a]=d[b]+1$ 。如圖:
![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217223008323-2035607542.png)
那麼有了這樣的規定,便有如下性質:
+ `d[a]%3==d[b]%3` 時,`a`,`b`是同一個物種。(操作1)
+ `((d[a]-d[b])%3+3)%3==1` 時,存在`a`吃`b`的關係。(這裡多次取模是為了保證左邊的值只可能為 $0,1,2$ )(操作2)
> 從上面的性質可以看出,兩個物種的關係與它們的模數(這題是 $mod3$ )餘多少關係密切相關,因此接下來我們也會著重考察兩個數之間的這種關係。
利用度數以及並查集,即可將各種動物之間的關係刻畫清楚:
這裡依然對`a`,`b`進行討論,為了方便,我們記`a`的祖宗(根節點)為`pa`,`b`的祖宗(根節點)為`pb`。
![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217224410185-2017022993.png)
+ 若`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$的數 )
![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217225624070-943305788.png)
+ 若`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]]`。
![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217232151218-1548099975.png)
如上圖,`pa`在一次操作中併入了`pb`。
而在另一次操作中,對`a`的進行了查詢(求祖宗),便有如下路徑壓縮的並更新`d[]`的過程:
遞迴地找出祖宗`pb`。
`pa`的祖宗就是`pb`,度數在合併的時候已經求出來了,所以更新 $0$。
`c`的父親節點是`pa`,合併的時候並沒有更新(因此記錄的是距離`pa`的度數),度數需要加上 $d[pa]$,然後進行路徑壓縮。
![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217233433590-208134830.png)
`a`的父親節點是`c`,在上一步更新了,所以度數加上 $d[c]$ 即可,類似的,進行路徑壓縮。
![](https://img2020.cnblogs.com/blog/2185228/202102/2185228-20210217233600925-924993944.png)
(這裡可能有點難理解,不過只要記住:所謂的`d[x]`指的是節點`x`相對於它父節點的度數即可)
> 不理解的地方可以結合程式碼理解
放上程式碼:(~~很短的~~)
```
#include
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>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<
程式碼
#include
using namespace std;
const int N=2e4+5;
unordered_map 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>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;
}
}