並查集 並查集

2022-06-12 15:21:09 字數 4299 閱讀 8685

本文參考了《挑戰程式設計競賽》和jennica的github題解**

陣列版:

int parent[max_n];

int rank[max_n];

void init(int n)

}int find(int x) else

}void union(int x, int y) else

}

結構體版:

struct node

node[max_n];

void init(int n)

}int find(int x) else

}void union(int x, int y) else

}

解題思路:

這個題是非常經典的並查集問題(種類並查集)。並查集作用:查詢a和b是否屬於同一組;合併a和b所在的組。注意並查集無法進行分割操作。利用題目中動物之間的相對關係可以建立乙個並查集。對每乙個元素,把它對父節點的關係用陣列rank[i]表示,即relation,作為權值。0:與父節點同類  1:吃父節點  2:被父節點吃。

路徑壓縮。為了使查詢效率提高,我們需要使樹的高度盡可能小,讓它只有一層,即在查詢的過程中,把樹中一條路徑上的所有點都連在根節點上,從而加快了查詢速度

,與此同時,還要更新與此節點有關的其他變數,比如此節點與父節點的權值(父節點變為根節點),該樹的高度等等。對於此題來講,就是要更新節點與父節點的關係,因為此節點的父節點已經變為根節點。那麼這裡如何推導出此節點與根節點的關係呢。假設此節點為x,其父節點為y,y的父節點為z。則:

rank[x]   rank[y]    x與z的關係權值

0 0 0=(i + j)%3

0 1 1=(i + j)%3

0 2 2=(i + j)%3

1 0 1=(i + j)%3

1 1 2=(i + j)%3

1 2 0=(i + j)%3

2 0 2=(i + j)%3

2 1 0=(i + j)%3

2 2 1=(i + j)%3

推導公式:rank[x] = (rank[x] + rank[y]) % 3; 即對於i節點,更新公式為:rank[i] = (rank[i] + rank[parent[i]]) % 3。不過還有更簡便的方法:模擬向量的運算x->z = x->y + y->z,所以rank[x] = (rank[x] + rank[y])% 3。對3取模是為了保證結果為0,1,2。

最後是集合的合併操作。合併操作並不複雜,複雜的是更新集合間的關係(即集合根節點的關係)。這裡有大神使用向量的形式來計算更新關係權值,假設需要合併x和y,查詢得知x和y的根節點分別為:x_p,y_p,如果將x_p連線到y_p,那麼rank[x_p]即為x_p與根節點y_p的關係。x_p->y_p = x_p->x + x->y + y->y_p = (-x->x_p + x->y + y->y_p)。所以更新公式為rank[x_p] = (-rank[x] + type - 1 + rank[y] + 3) % 3。(加上3是為了防止出現負數的情況;對3取模是為了保證結果的取值範圍)。type即為輸入的num。type為1,x與y同類,type為2,x吃y。 

solution:from 專注如一

#include #include 

#include

using

namespace

std;

const

int max_n = 50000 + 10

;int

parent[max_n], rank[max_n];

void init(intn)}

int find(int

x)

int y =find(parent[x]);

rank[x] = (ran[x] + ran[parent[x]]) % 3

;

return parent[x] =y;

}int union(int x, int y, int

type)

parent[x_p] =y_p;

rank[x_p] = (-rank[x] + type - 1 + rank[y] + 3) % 3

;

return0;

}int

main()

printf(

"%d\n

", res);

return0;

}

此題還有一種解法。對於每只動物i建立3個元素i->a,i->b,i->c,並用這3*n個元素建立並查集。i->x表示動物i屬於種類x;並查集裡的每乙個組內表示組內所有元素代表的情況都同時發生或不發生。例如i->a和y->b屬於一組,那麼就說明如果i屬於a那麼j一定屬於b,相同的如果j屬於b那麼i一定屬於a。

n = 1: x和y屬於一類,那麼合併x->a和y->a,x->b和y->b,x->c和y->c。

n = 2: x吃y                  那麼合併x->a和y->b,x->b和y->c,x->c和y->a。

另外要注意的是在合併之前要檢查合併操作是否會產生矛盾。

solution 2

#include #include 

#include

using

namespace

std;

const

int max_n = 50000 * 3 + 5

;//元素x,x + n,x + 2 * n分別代表x->a,x->b,x->c

intparent[max_n], rank[max_n];

void init(intn)}

int find(int

x)

return parent[x] =find(parent[x]);

}bool same(int x, int

y)void union(int x, int y, int

type)

if(rank[x] else

}int

main()

if(type == 1

) else

} else

else}}

printf(

"%d\n

", res);

return0;

}

解題思路:

典型的並查集問題,較上一題簡單。思路就是開闢兩個陣列,乙個用來儲存父節點,乙個用來儲存狀態:是否被修復。假設現在修復電腦p,那麼p狀態就更改為已修復。然後在所有電腦中遍歷一遍,只尋找滿足以下條件的電腦:1. 已修復電腦(假設為q) 2. p和q 距離小於最大距離  3. p和q根節點不同(即不屬於同一棵樹),那麼就合併p和q所在的樹。查詢是否可以通訊就很簡單了,如果p和q的根節點相同,那麼就可以通訊,否則通訊失敗。這裡要注意並查集不是只有一棵樹,而是乙個森林,包含了很多樹

solution :

#include #include 

#include

using

namespace

std;

const

int max_n = 1000 + 10

;int

x[max_n], y[max_n];

intparent[max_n];

bool

fixed

[max_n];

intn, d;

bool close(int a, int

b)void init(intn)}

int find(int

a)

return parent[a] =find(parent[a]);

}void union(int a, int

b)int

main()

char op[5

];

inta, b;

while(scanf("

%s", op) !=eof)}}

} else

else}}

return0;

}

解題思路:

poj1182的簡化版,模板一套就可以了。

solution

並查集入門(普通並查集 帶刪除並查集 關係並查集)

什麼是並查集?通俗易懂的並查集詳解 普通並查集 基礎並查集 例題 題解 how many tables problem description lh boy無聊的時候很喜歡數螞蟻,而且,還給每乙隻小螞蟻編號,通過他長期的觀察和記錄,發現編號為i的螞蟻會和編號為j的螞蟻在一起。現在問題來了,他現在只有...

並查集,帶權並查集

題意 ignatius過生日,客人來到,他想知道他需要準備多少張桌子。然而一張桌子上面只能坐上相互熟悉的人,其中熟悉可定義成為a與b認識,b與c認識,我們就說a,b,c相互熟悉 例如a與b熟悉and b與c熟悉,d與e熟悉,此時至少需要兩張桌子。輸入 t表示樣例個數,n表示朋友個數,朋友從1到n編號...

初學並查集 並查集1

現在,我是用乙個初學者的眼光來寫並查集,此文最初寫於我學並查集的那天,後經過多次修改。1 並查集是什麼 並查集是乙個具有多個連通分支的圖,他擁有合併兩個連通分支,和查詢兩個元素是否位於同乙個連通分支的功能。2 並查集的簡單應用 並查集解決什麼問題 假如有一些點,你知道哪些點是直接相連的,但實際上間接...