並查集——以nuist OJ P1648煉丹術為例

chanxe發表於2022-05-14

並查集

定義:並查集是一種樹形的資料結構,用於處理一些不相交集合的合併及查詢問題

主要構成:

並查集主要由一個整型陣列pre[]和兩個函式find()、join()構成。

陣列pre[]記錄了每個點的前驅結點是誰,函式find(x)用於查詢指定結點x屬於哪個集合,函式join(x,y)用於合併兩個結點x和y。

作用:

並查集的主要作用是求聯動分支數。

代表元:

用集合中的某個元素來代表這個集合,則該元素稱為此集合的代表元

find()函式的定義與實現:

 int find(int x){
  while(pre[x]!=x) //如果代表元不是自己
  x = pre[x]; //x繼續向上找其上級,直到找到代表元為止
  return x;
 }

join()函式的定義與實現:

 void join(int x,int y){
  int fx = find(x),fy=find(y);
  if(fx!=fy)
  pre[fx] = fy;
 }

路徑壓縮演算法:將x到根節點路徑上的所有點的上級都設為根節點

 //遞迴實現
 int find(int x){
  if(pre[x] == x) return x;
  return pre[x] = find(pre[x]);
 }
 //迴圈實現
 int find(int x) {
  while(x!=pre[x])x=pre[x]=pre[pre[x]];
  return x;
 }

總結:

1、用集合中的某個元素來代表這個集合,則該元素稱為此集合的代表元; 2 、一個集合內的所有元素組織成以代表元為根的樹形結構; 3 、對於每一個元素 x,pre[x] 存放 x 在樹形結構中的父親節點(如果 x 是根節點,則令pre[x] = x); 4 、對於查詢操作,假設需要確定 x 所在的的集合,也就是確定集合的代表元。可以沿著pre[x]不斷在樹形結構中向上移動,直到到達根節點。 因此,基於這樣的特性,並查集的主要用途有以下兩點: 1、維護無向圖的連通性(判斷兩個點是否在同一連通塊內,或增加一條邊後是否會產生環); 2、用在求解最小生成樹的Kruskal演算法裡。

 //程式碼彙總
 const int N = 1005 //指定並查集所能包含的元素個數
 int pre[N];
 int rank[N];
 void init(int n){
  for(int i=0;i<n;i++){
  pre[i] = i;//每個節點的上一級都是自己
  rank[i] = 1;
  }
 }
 int find(int x){
  if(pre[x] == x) return x;
  return find(pre[x]);
 }
 int find(int x){
  if(pre[x] == x) return x;
  return pre[x] = find(pre[x]);
 }
 //判斷兩個結點是否連通
 bool isSame(int x,int y){
  return find(x) == find(y);
 }
 
 bool join(int x,int y){
  x = find(x);
  y = find(y);
  if(x == y) return false;
  if(rank[x] >rank[y]) pre[y] = x;
  else{
  if(rank[x] == rank[y]) rank[y]++;
  pre[x] = y;
  }
  return true;
 }

例:煉丹術

題目描述

三水最近在學習煉丹術。但是眾所周知煉丹術是一門危險的學科,需要大量的調參才能保證安全。好在三水在洗衣機裡面找到了一張失傳已久的圖紙,裡面記錄了若干種材料的藥性。這張圖紙上記錄了 n種不同的藥材,對於每種藥材,都需要恰好一種藥材來使其穩定 (這種藥材可能是其自身,即這種藥材本身就很穩定)。三水想知道,通過這張圖紙,可以得到多少種不同的穩定的丹方。保證每種藥材只會作為穩定劑出現一次。

我們認為一個丹方是從 n種藥材中選擇若干種 (不為 0 ),兩個丹方被認為是不同的當且僅當存在一種藥材在其中一個丹方中且不在另一箇中。我們稱一個丹方是穩定的,當且僅當所有出現在丹方中的藥材的穩定劑也在藥材中。

因為輸出結果可能很大,所以答案對 998244353 取模。

輸入描述

第一行一個數字 nn , 表示有 n (1\leqslant n\leqslant 10^6)n(1⩽n⩽106) 種不同的藥材。 接下來一行 n個數字,第 i數字 a_i (1\leqslant a_i\leqslant n)a**i(1⩽a**in) 表示藥材 ii 的穩定劑是 a_ia**i,保證輸入是 11 到 nn 的一個全排列。

輸出描述

一個整數 nn ,表示答案對 998244353 取模的結果。

樣例輸入
  6 2 3 4 5 6 1 
樣例輸出
  1
思路:

 

AC程式碼
 #include<cstdio>
 #include<iostream>
 const int MAXN=1000005;
 const int INF=0x3f3f3f3f;
 const int mod=998244353;
 
 using namespace std;
 
 int pre[MAXN], a[MAXN];
 
 int find(int x) {
  while(x!=pre[x])x=pre[x]=pre[pre[x]];
  return x;
 }
 int pow(int n) {
  int ans=1,base=2;
  for(int i=1;i<=n;++i) {
  ans=(ans*base)%mod;
  }
  return ans;
 }
 int main() {
  int n;
  scanf("%d",&n);
  for(int i=1;i<=n;++i) {
  scanf("%d",&a[i]);
  pre[i]=i;//初始化查陣列
  }
  for(int i=1;i<=n;++i) {
  int u=find(i), v=find(a[i]);//通過字首陣列更新並查集,查詢過程中進行路徑壓縮
  if(u!=v)pre[u]=v; //合併相關聯集合
  }
  int cnt=0;
  for(int i=1;i<=n;++i) { //記錄不同集合個數
  if(pre[i]==i) cnt++;
  }
  printf("%d",pow(cnt)-1);
  return 0;
 }
 

相關文章