1. 程式人生 > 實用技巧 >擴充套件域並查集

擴充套件域並查集

開始填坑力

並查集是一種樹型的資料結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。帶路徑壓縮的並查集的時間複雜度已被證明是 \(O(\alpha(n))\) 的。這個函式的值在當 \(n\) 實際有意義的範圍內不會大於 \(4\)

但是對於多組關係,普通的並查集就難以維護了。

這個時候就可以使用擴充套件域並查集


擴充套件域並查集

擴充套件域並查集常用來維護多組關係的集合合併問題,在實現上比帶權並查集要簡單一些 (個人覺得)

主要思想

其實很簡單,將一個點拆分成多個點,在不同的關係中使用,維護多個關係。

程式碼實現時,可以通過的對點的序號\(+\)點的總個數來實現拆點操作。

下面看例題:


實際應用

NOIp提高組2010:關押罪犯

題目描述

\(S\) 城現有兩座監獄,一共關押著 \(N\) 名罪犯,編號分別為\(1- N\)

他們之間的關係自然也極不和諧。

很多罪犯之間甚至積怨已久,如果客觀條件具備則隨時可能爆發衝突。

我們用“怨氣值”(一個正整數值)來表示某兩名罪犯之間的仇恨程度,怨氣值越大,則這兩名罪犯之間的積怨越多。

如果兩名怨氣值為 \(c\) 的罪犯被關押在同一監獄,他們倆之間會發生摩擦,並造成影響力為 \(c\) 的衝突事件。

每年年末,警察局會將本年內監獄中的所有衝突事件按影響力從大到小排成一個列表,然後上報到 \(S\)\(Z\) 市長那裡。

公務繁忙的 \(Z\) 市長只會去看列表中的第一個事件的影響力,如果影響很壞,他就會考慮撤換警察局長。

在詳細考察了 \(N\) 名罪犯間的矛盾關係後,警察局長覺得壓力巨大。

他準備將罪犯們在兩座監獄內重新分配,以求產生的衝突事件影響力都較小,從而保住自己的烏紗帽。

假設只要處於同一監獄內的某兩個罪犯間有仇恨,那麼他們一定會在每年的某個時候發生摩擦。那麼,應如何分配罪犯,才能使 \(Z\) 市長看到的那個衝突事件的影響力最小?這個最小值是多少?

輸入格式

第一行為兩個正整數 \(N\)\(M\),分別表示罪犯的數目以及存在仇恨的罪犯對數。

接下來的 \(M\) 行每行為三個正整數 \(a_j,b_j,c_j\)

,表示 \(a_j\) 號和 \(b_j\) 號罪犯之間存在仇恨,其怨氣值為 \(c_j\)

資料保證 \(1≤aj<bj<N,0<cj≤1000000000\) 且每對罪犯組合只出現一次。

輸出格式

輸出共 \(1\) 行,為 \(Z\) 市長看到的那個衝突事件的影響力。

如果本年內監獄中未發生任何衝突事件,請輸出 \(0\)

資料範圍

\(N\leq 20000,M \leq 100000,\ 1≤aj<bj<N,\ 0<cj≤1000000000\)

樣例

in:

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

out:

3512

例題解析:

題目求最大影響的最小值,由貪心我們可以可以知道,我們要儘量不讓更大的衝突發生,考慮排序,先保證讓大的衝突不發生。

若是隻有一個監獄,那就直接使用一個普通並查集,維護就好。然而此題有兩個監獄,一個人放在這個監獄,另一個人必定放在另外一個監獄裡面。

我們可以考慮把第 \(i\) 個人拆成 \(i\)\(i+1\)。一個維護友好的集合,一個維護對立的集合。每次查詢一個關係,兩人應該分別在兩個集合中,若查詢到兩個人在同一集合,則這個事件無法避免的會發生。

放code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=1000010;

struct iee
{
	int x,y;
	ll k;
} rela[N];
bool cmp(iee a,iee b)
{
	return a.k>b.k;
}

int n,m;
int parent[N];
void init();
int find(int x);
int union_(int x,int y);
int check(int x,int y);

int main()
{
	init();
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d%lld",&rela[i].x,&rela[i].y,&rela[i].k);
	sort(rela+1,rela+1+m,cmp);
	for(int i=1;i<=m;i++)
	{
		if(check(rela[i].x,rela[i].y))
		{
			cout<<rela[i].k;
			exit(0);//貪心,若在同一集合直接輸出並退出
		}
		else
		{
			union_(rela[i].x+n,rela[i].y);
			union_(rela[i].x,rela[i].y+n);//擴充套件域並查集核心:x + n
		}
	}
	cout<<0;
	return 0;
}

void init()
{
	for(int i=0;i<N;i++)
		parent[i]=i;
}

int find(int x)
{
	int x_root=x;
	while(x_root!=parent[x_root])
	{
		x_root=parent[x_root];
	}
	while(x!=x_root)
	{
		int tmp=parent[x];
		parent[x]=x_root;
		x=tmp;
	}
	return x_root;
}

int union_(int x,int y)
{
	int x_root=find(x);
	int y_root=find(y);
	if(x_root==y_root) return 0;//合併失敗
	parent[x_root]=y_root;
	return 1;
}

int check(int x,int y)
{
	return find(x)==find(y);
}