luogu P1551 親戚(並查集入門)
這是一個並查集模板。
說一下並查集,雖然我也是剛剛學會沒幾天。。。
並查集是個樹形結構的資料結構,主要用於合併兩個不相交的集合;
首先是初始化,其中的f陣列表示第i點的父親
for(int i=1;i<=n;i++)f[i] = i;
並查集分為合併與查詢,我習慣寫成merge(合併),query(查詢)
合併的話,首先呼叫query函式去查詢兩點的祖先節點,如果不是同一祖先,兩個集合就沒有關聯,然後就將其祖先節點所代表的集合合併,(我所用的rand是讓合併的方向打亂,不易被卡),好像還有按秩合併,然而我還不會,以後再補這個鍋吧,,,
void merge(int x,int y){
int f1 = query(x);
int f2 = query(y);
if(f1 != f2){
if(rand()%2)f[f1] = f2;
else f[f2] = f1;
}
return;
}
接下來就是query(查詢操作),不斷遞迴查詢父親節點,直到找到祖先節點,後面的是路徑壓縮,因為尋找路途上的所有點的祖先節點相同,所以在返回的時候把每個點去都放入同一集合,可以極大的縮短查詢時間,寫並查集的話一定帶上路徑壓縮,不然會被卡。不過好像有卡路徑壓縮的題,只是聽dalao說過,具體問題具體分析吧。
int query(int x){
if(x == f[x])return x;
else return f[x] = query(f[x]);//路徑壓縮
}
下面是這道題,就是一個並查集根據題目要求做就可以了
題目背景
若某個家族人員過於龐大,要判斷兩個是否是親戚,確實還很不容易,現在給出某個親戚關係圖,求任意給出的兩個人是否具有親戚關係。
題目描述
規定:x和y是親戚,y和z是親戚,那麼x和z也是親戚。如果x,y是親戚,那麼x的親戚都是y的親戚,y的親戚也都是x的親戚。
輸入輸出格式
輸入格式:
第一行:三個整數n,m,p,(n<=5000,m<=5000,p<=5000),分別表示有n個人,m個親戚關係,詢問p對親戚關係。
以下m行:每行兩個數Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有親戚關係。
接下來p行:每行兩個數Pi,Pj,詢問Pi和Pj是否具有親戚關係。
輸出格式:
P行,每行一個’Yes’或’No’。表示第i個詢問的答案為“具有”或“不具有”親戚關係。
輸入輸出樣例
輸入樣例#1: 複製
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
輸出樣例#1: 複製
Yes
Yes
No
附AC程式碼:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int maxn = 500521;
int n,m,p;
int mi;
int mj;
int pi;
int pj;
int f[maxn];
inline int read(){//讀入優化
int x=0;int f=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-f;
c=getchar();
}
while(c<='9'&&c>='0'){
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
int query(int x){//查詢操作
if(x == f[x])return x;
else return f[x] = query(f[x]);//路徑壓縮
}
void merge(int x,int y){//合併操作
int f1 = query(x);
int f2 = query(y);
if(f1 != f2){
if(rand()%2)f[f1] = f2;
else f[f2] = f1;
}
return;
}
int main(){
n = read();
m = read();
p = read();
for(int i=1;i<=n;i++)f[i] = i;//初始化,不能丟
for(int i=1;i<=m;i++){
mi = read();
mj = read();
merge(mi,mj);
}
for(int i=1;i<=p;i++){
pi = read();
pj = read();
if(query(pi) == query(pj)){
printf("Yes\n");
}
else printf("No\n");
}
return 0;
}