1. 程式人生 > 實用技巧 >SMZX十日遊(第二階段並查集)並查集學習筆記

SMZX十日遊(第二階段並查集)並查集學習筆記

創作背景

今天是在SMZX的第四天,終於學習新知識了,感動。所以,當然要寫部落格好好總結一番

並查集原理

近日,一款遊戲風靡世界,那就是:吃 鴨。
為了取得勝利,就有許多玩家團結在一起,形成了多個非法組織。
眾所周知,很多組織的參與者都是被自己的朋友拉進來的,並且不能更換服飾,所以2個人見面時,為了不誤傷友軍,都要詢問自己的朋友發起者是誰才敢開打

int find(int k)
{
	if(f[k]==k) return k;//如果是被自己召集進來的,那發起人就是自己
     return find(f[k]);//如果不是就委託朋友找到拉他的朋友
}

如果這樣一個一個問,有可能很長時間都無法知道自己的組織,於是,\(路徑壓縮\)

就出現了

int find(int x)
{
	if (fa[x] == x) return x;
	return fa[x] = find(fa[x]); 
//	fa[x] = find(fa[x]);在搜尋組織的發起者時,就順帶記住自己的組織的發起者
//	return fa[x];
}

這時候有一對V8 情侶,一不小心參加進不同的組織,為了讓他們在一起,官方讓兩個組織合併在一起,如果新創組織,那其他不知道這件事的雙方的組織的人可能就開打,於是便讓一個發起者服從另一個發起者,那誰服從誰呢?誰規模大就服從誰

void merge2(int x, int y)
{
	int rx = find(x), ry = find(y);//尋找雙方的發起者
	if (size[rx] > size[ry]) {//比較規模
		fa[ry] = rx;//設定發起者
		size[rx] += size[ry];//人數合併
	} else {
		fa[rx] = ry;
		size[ry] += size[rx];
	}
}

其實這裡“吃鴨”遊戲,就已經涵蓋了並查集的基本操作,完整程式碼如下:模板題傳送門

#include<bits/stdc++.h>
using namespace std;
int f[10086],r,k,n,m,p1,p2,p3;
int find(int k)
{
	if(f[k]==k) return k;
    else return f[k]=find(f[k]);
}//路徑壓縮
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	f[i]=i;
	for(int i=1;i<=m;i++)
	{
		cin>>p1>>p2>>p3;
		if(p1==1)
			f[find(p2)]=find(p3);
		else if(p1==2)
			if(find(p2)==find(p3))
			cout<<"Y"<<endl;
			else
			cout<<"N"<<endl;
   }
	return 0;
}

小試牛刀

學會的同學戳這裡小試牛刀
本人用下面這題進行講解

朋友

題目背景

小明在A公司工作,小紅在B公司工作。

題目描述

這兩個公司的員工有一個特點:一個公司的員工都是同性。
A公司有N名員工,其中有P對朋友關係。B公司有M名員工,其中有Q對朋友關係。朋友的朋友一定還是朋友。
每對朋友關係用兩個整數(Xi,Yi)組成,表示朋友的編號分別為Xi,Yi。男人的編號是正數,女人的編號是負數。小明的編號是1,小紅的編號是-1.
大家都知道,小明和小紅是朋友,那麼,請你寫一個程式求出兩公司之間,通過小明和小紅認識的人最多一共能配成多少對情侶。(包括他們自己)

輸入格式

第1行,4個空格隔開的正整數N,M,P,Q。
之後P行,每行兩個正整數Xi,Yi。
之後Q行,每行兩個負整數Xi,Yi。

輸出格式

一行,一個正整數,表示通過小明和小紅認識的人最多一共能配成多少對情侶。(包括他們自己)

輸入輸出樣例

輸入

4 3 4 2
1 1
1 2
2 3
1 3
-1 -2
-3 -3

輸出

2

說明/提示

對於30%資料,N,M<=100,P,Q<=200
對於80%資料,N,M<=4000,P,Q<=10000.
對於全部資料,N,M<=10000,P,Q<=20000。

思路

簡簡單單的並查集,首先搜尋兩間的朋友關係網。注意:由於女生是負數,所有要把負數轉為正數
然後:只要與小明小紅有關係的就統計下來,按最少的輸出畢竟沒有一夫多妻或一妻多夫

Code

#include<bits/stdc++.h>
using namespace std;
int ans=1,sum=1,f2[100005],f1[100005],p,q,n,m,p2,p3;
int find1(int k)
{
	if(f1[k]==k || f1[k]==0) return k;
    return f1[k]=find1(f1[k]);
    
}
int find2(int k)
{
	if(f2[k]==k || f2[k]==0) return k;
    return f2[k]=find2(f2[k]);
}
int main()
{
	cin>>n>>m>>p>>q;
	for(int i=1;i<=p;i++)
	{
		cin>>p2>>p3;
		f1[max(find1(p2),find1(p3))]=min(find1(p3),find1(p2));//這裡如果把發起者算小,那麼後面查詢情侶會畢竟輕鬆,但大一個沒有問題
	}
    for(int i=1;i<=q;i++)
	{
		cin>>p2>>p3;
		p2=0-p2;//把負數變成正數
		p3=0-p3;
		f2[max(find2(p2),find2(p3))]=min(find2(p3),find2(p2));
    }
    for(int i=2;i<=max(n,m);i++)
    {
    	if(find1(f1[i])==1) ans++;//統計人數
    	if(find2(f2[i])==1) sum++;
    }
    	
    cout<<min(ans,sum);//輸出
	return 0;
}

THE END

其實,並查集……難度也還好吧
注:感謝這篇給我啟發的部落格