演算法訓練 並查集

2021-08-15 16:09:52 字數 4329 閱讀 9401

這個是我做 kuangbin帶你飛專題訓練的第三個專題,對應的是 kuangbin帶你飛專題五,這個專題是並查集專題,包含14道題目。

並查集屬於樹形結構,是一種用來管理元素分組情況的資料結構。並查集可以高效的進行如下操作:

值得注意的是,並查集雖然可以進行合併操作,但是不能進行分割操作。當然了這並不意味著包含分割操作的題目就不能用並查集解決(如 zoj-3261 connections in galaxy war)。

並查集的綜合應用

下面是並查集實現的例子。在例子中,用編號代替每個元素。陣列 par 表示的是父節點的編號,當 par[x] = x 時,x 是所在樹的根

int par[max_n], rank[max_n];

void init(int n)

}int find(int x)

void unite(int x, int y)

}}bool same(int x, int y)

以上就是最簡單的並查集的實現,除了這樣的,還有一種帶權並查集。

經典例題 hdu-1213 how many tables

解題思路:沒什麼好說的,就是以上並查集模板的簡單應用

#include 

#include

using

namespace

std;

intconst maxn = 1005;

int n,m;

int rankk[maxn], par[maxn];

// 並查集

void init(int n)

}int find_node(int x)

void unite(int x, int y)

}}int main()

uniqu.clear();

for(int i = 1;i <= n;i++)

uniqu.insert(find_node(i));

printf("%d\n",uniqu.size());

if (i < tcs)

scanf("\n");

}return

0;}

避免退化

在樹形資料結構中,如果發生退化,那麼複雜度就會變得很高。因此,有必要想辦法避免退化的發生,在並查集中,有如下的方法可以避免退化:

路徑壓縮

此外,通過路徑壓縮,可以使並查集變得更加高效。路徑壓縮是:對於每個節點,一旦向上走到了一次根節點,那麼就把這個點到父節點的邊改為直接連向根。在此之上,不僅僅是所查詢的節點,在查詢過程中向上所經過的所有節點,都改為直接連到根上。這樣再次查詢這些節點的時候,很快就可以知道根是誰了。

再使用這種簡化方法時,為了簡單起見,即使樹的高度發生了變化,也不修改 rank 的值。當然了有時候,rank 不僅僅作為樹的高度,還可以根據題意,賦予特殊的含義,比如說 zoj-3261 connections in galaxy war.

並查集的結構

並查集也是使用樹形結構實現的,不過不是二叉樹。每乙個元素對應乙個節點,每個組對應乙個棵樹。在並查集中,哪個節點是哪個節點的父節點以及樹的形狀等資訊無需多加關注,整體組成乙個樹形結構才是重要的。

所以,如果並查集中的元素十分稀疏,在進行離散化的時候,一般不必考慮各個元素的相對大小,只要可以符合題意組成一棵樹即可。

經典例題 poj 1733 parity game

解題思路:這道題是帶權並查集的應用,首先要會推導路徑壓縮和合併分組時的公式,其次,這道題的資料比較稀疏,因此需要離散化,如上述所說,各個節點之間的關係並不嚴格,所以只要用 map 簡單的離散化一下就可以了。

#include 

#include

#include

#include

#include

using

namespace

std;

intconst maxn = 10010;

int n, m, mi = 1;

// 並查集

struct nodegame[maxn];

void init(void)

}int find_node(int x)

bool unite(int x,int y, int rea)

else

}int main()

printf("%d\n", ans);

return

0;}

並查集作為一種資料結構,很少會單獨考察,通常是和其他的演算法一同考察。

並查集與動態規劃

經典例題 poj-1417 true liars

解題思路:這道題比較有難度,關於並查集的部分倒是不難想。根據題意可以知道,說 yes 的人與被他說的人是同類,說 no 的人與被他說的人不是同類,這裡可以用帶權並查集,權值是當前節點與父節點的關係,關係有兩種,一種是 0 表示同類,另一種是 1 表示不是同類。路徑壓縮時權值的的改變的方式是,當前節點與新父節點的關係等於 當前節點與舊父節點的關係加上舊父節點與舊…爺爺節點的關係 模2。在合併分組時權值改變的方式是,節點 x 加上節點 y 的權值再加上描述中兩個節點之間的關係模2。

另乙個知識點是動態規劃,在經過以上步驟之後,現在有很多個連通分量,每個連通分量裡面應該有兩類,但是不知道哪一類是好人哪一類是壞人,所以要在每乙個連通分量中取一類,最終組成一組,看看這組的人數是不是與好人人數相同。如果這樣的取法僅有一種,則代表有正解。用 dp[i][j] 來表示從前 i 個連通分量中取 j 個人,一共有多少種取法,初始化 dp[0][0] = 1,餘下所有為 0.確定有正解之後,再用 dp這個陣列來逆推從每乙個聯通分量中選擇了哪一類

#include 

#include

#include

using

namespace

std;

intconst maxp = 605;

int dp[maxp][maxp], bag[maxp][2];

// 對映

map cc;

int cnt;

void cc_insert(int x, int rea)

// 並查集

struct nodes[maxp];

void init(int n)

}int find_node(int x)

void unite(int x, int y, int rea)

}bool same(int x, int y)

int main()

// 將連通分量對映到 map 上,重新編號

cnt = 0;

memset(bag, 0, sizeof(bag));

for (int i = 1;i <= p1+p2;i++)

memset(dp, 0, sizeof(dp));

dp[0][0] = 1;

for (int i = 1;i <= cnt;i++)

}// 逆推路徑並輸出

if (dp[cnt][p1] == 1)

for (int i = 1;i <= p1+p2;i++)

printf("end\n");

}else

printf("no\n");

}return

0;}

並查集與貪心演算法

經典例題:poj-1456 supermarket

解題思路:先將給的資料按照 px 從大到小排序,再先按照最大售賣期限初始化並查集,然後對於結構體陣列中的每個成員,如果它售賣期限在並查集中的父節點大於 0,那麼 i 將會在這一天售賣,同時將其父節點減一,代表和 i 的期限在同一天的商品,最晚會在這一天之前賣出。如此,便可求出最大利潤。

#include 

#include

#include

using

namespace

std;

intconst maxn = 10005;

struct node

}prdct[maxn];

// 並查集

int par[maxn];

void init(int n)

int find_node(int x)

int main()

sort(prdct, prdct+n);

init(maxd);

int ans = 0;

for (int i = 0;i < n;i++)

}printf("%d\n", ans);

}return

0;}

演算法訓練 安慰奶牛 kruskal 並查集)

farmer john變得非常懶,他不想再繼續維護供奶牛之間供通行的道路。道路被用來連線n個牧場,牧場被連續地編號為1到n。每乙個牧場都是乙個奶牛的家。fj計畫除去p條道路中盡可能多的道路,但是還要保持牧場之間 的連通性。你首先要決定那些道路是需要保留的n 1條道路。第j條雙向道路連線了牧場sj和e...

並查集演算法

所謂並查集,它是乙個集合,這個集合的元素也是集合,他支援三種操作 makeset x 建立乙個只有乙個元素x的集合x0,將這個集合放入並查集中 findset x 在並查集中尋找乙個元素s 注意並查集的元素s也是集合 滿足 x屬於s union x,y 將並查集中的元素s1,s2合併,其中x屬於s1...

並查集演算法

並查集是一種樹型的資料結構,用於處理一些不相交集合 disjoint sets 的合併及查詢問題。常常在使用中以森林來表示。讓每個元素構成乙個單元素的集合,也就是按一定順序將屬於同一組的元素所在的集合合併。1 makeset s 建立乙個新的並查集,包含s個單元素集合。2 union x,y 把x ...