[演算法學習筆記] 並查集

SXqwq發表於2024-04-24

提示:本文並非並查集模板講解,是在模板基礎上的進一步理解以及擴充。

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\)
image

不難發現,\(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;
}

待更。

相關文章