提示:本文並非並查集模板講解,是在模板基礎上的進一步理解以及擴充。
Review
並查集可以用來維護集合問題。例如,已知 \(a,b\) 同屬一個集合,\(b,c\) 同屬一個集合。那麼 \(a,b,c\) 都屬一個集合。
並查集分為 合併,查詢 操作。定義 \(fa_i\) 表示點 \(i\) 的父親。為了降低複雜度,在 find 操作向上遞迴查祖先時我們同步將 \(fa_i\) 更改為 \(i\) 的祖先。這就是所謂路徑壓縮。
對於合併,為了方便直接合並即可。當然也可以按秩合併最佳化,雖然我認為最佳化效果不大。
種類並查集
並查集的傳遞性非常強大,對於普通的傳遞關係問題,並查集可以輕鬆解決。但是,對於有種類關係的,比如"敵人的敵人是朋友” 此類關係又該如何維護呢?
我們來看一到例題。
BOI2003 團伙
Description
現在有 \(n\) 個人,他們之間有兩種關係:朋友和敵人。我們知道:
- 一個人的朋友的朋友是朋友
- 一個人的敵人的敵人是朋友
現在要對這些人進行組團。兩個人在一個團體內當且僅當這兩個人是朋友。請求出這些人中最多可能有的團體數。
不難發現,本題的關鍵在於維護 “敵人的敵人是朋友” 這種關係。如果沒有這層限制,那本題就是樸素的並查集模板題。
實際上,我們只需要開兩倍並查集。對於 \(\forall x,y\) ,若 \(x,y\) 是朋友,合併 \(x,y\) 即可。這是普通並查集操作。反之,若 \(x,y\) 是敵人,分別合併 \(x,y+n\),\(x+n,y\) 即可。這樣我們就解決了問題。
接下來我們將透過畫圖,來解釋這樣的合併方式是如何工作的。
模擬樣例:已知 \(1,2\) 是敵人關係,\(2,4\) 是敵人關係。按照要求,\(1,4\) 應是朋友關係。\(n=4\)
不難發現,\(4\) 透過敵人 \(2\) ,\(2\) 與敵人 \(1\) 。\(4\) 與 \(1\) 在同一種類裡相連,朋友關係。這就是最簡單的種類並查集的工作原理。
這樣,我們就解決了本題。
程式碼
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n,m;
int fa[N];
int dist[N];
int ans = 0;
vector <int> Edge;
void Init()
{
for(int i=1;i<=n*2;i++)
{
fa[i] = i;
dist[i] = 1;
}
}
int find(int x)
{
if(x == fa[x]) return x;
fa[x] = find(fa[x]);
return fa[x];
}
void merge(int i,int j)
{
int x = find(i),y = find(j);
if(x == y) return;
if(dist[x] < dist[y]) fa[x] = fa[y];
else fa[y] = fa[x];
if(dist[x] == dist[y] && x != y) dist[y] ++;
}
int main()
{
// freopen("input.txt","r",stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
Init();
for(int i=1;i<=m;i++)
{
char op;
int p,q;
cin>>op>>p>>q;
if(op == 'F')
{
merge(p,q);
}
else
{
merge(p,q+n);
merge(q,p+n);
}
}
for(int i=1;i<=n;i++)
{
// int f = find(i);
if(fa[i] == i) ans ++;
}
cout<<ans<<endl;
return 0;
}
待更。