POJ 并查集 题目汇总 ——czyuan原创(转)
繼續數據結構的復習,本次的專題是:并查集。
????? 并查集,顧名思義,干的就是“并”和“查”兩件事。很多與集合相關的操作都可以用并查集高效的解決。
??? ?? 兩個操作代碼:
?????? int Find(int x)
?????? {
????????? if (tree[x].parent != x)
????????? {
????????????? tree[x].parent = Find(tree[x].parent);
????????? }
????????? return tree[x].parent;
?????? }
?????? void Merge(int a, int b, int p, int q, int d)
?????? {
????????? if (tree[q].depth > tree[p].depth) tree[p].parent = q;
????????? else
????????? {
????????????? tree[q].parent = p;
????????????? if (tree[p].depth == tree[q].depth) tree[p].depth++;
????????? }
?????? }
?????? 其中Find()函數用了路徑壓縮優化,而Merge()函數用了啟發式合并的優化(個人感覺有了路徑壓縮,啟發式合并優化的效果并不明顯,而經常因為題目和代碼的限制,啟發式合并會被我們省略)。
?????? 提到并查集就不得不提并查集最經典的例子:食物鏈。
?????? POJ 1182 食物鏈
?? ????http://acm.pku.edu.cn/JudgeOnline/problem?id=1182
?????? 題目告訴有3種動物,互相吃與被吃,現在告訴你m句話,其中有真有假,叫你判斷假的個數(如果前面沒有與當前話沖突的,即認為其為真話)
這題有幾種做法,我以前的做法是每個集合(或者稱為子樹,說集合的編號相當于子樹的根結點,一個概念)中的元素都各自分為A, B, C三類,在合并時更改根結點的種類,其他點相應更改偏移量。但這種方法公式很難推,特別是偏移量很容易計算錯誤。
下面來介紹一種通用且易于理解的方法:
首先,集合里的每個點我們都記錄它與它這個集合(或者稱為子樹)的根結點的相對關系relation。0表示它與根結點為同類,1表示它吃根結點,2表示它被根結點吃。
那么判斷兩個點a, b的關系,我們令p = Find(a), q = Find(b),即p, q分別為a, b子樹的根結點。
?????? 1. 如果p != q,說明a, b暫時沒有關系,那么關于他們的判斷都是正確的,然后合并這兩個子樹。這里是關鍵,如何合并兩個子樹使得合并后的新樹能保證正確呢?這里我們規定只能p合并到q(剛才說過了,啟發式合并的優化效果并不那么明顯,如果我們用啟發式合并,就要推出兩個式子,而這個推式子是件比較累的活...所以一般我們都規定一個子樹合到另一個子樹)。那么合并后,p的relation肯定要改變,那么改成多少呢?這里的方法就是找規律,列出部分可能的情況,就差不多能推出式子了。這里式子為 : tree[p].relation = (tree[b].relation - tree[a].relation + 2 + d) % 3; 這里的d為判斷語句中a, b的關系。還有個問題,我們是否需要遍歷整個a子樹并更新每個結點的狀態呢?答案是不需要的,因為我們可以在Find()函數稍微修改,即結點x繼承它的父親(注意是前父親,因為路徑壓縮后父親就會改變),即它會繼承到p結點的改變,所以我們不需要每個都遍歷過去更新。
?????? 2. 如果p = q,說明a, b之前已經有關系了。那么我們就判斷語句是否是對的,同樣找規律推出式子。即if ( (tree[b].relation + d + 2) % 3 != tree[a].relation ), 那么這句話就是錯誤的。
?????? 3. 再對Find()函數進行些修改,即在路徑壓縮前紀錄前父親是誰,然后路徑壓縮后,更新該點的狀態(通過繼承前父親的狀態,這時候前父親的狀態是已經更新的)。
?????? 核心的兩個函數為:
?????? int Find(int x)
?????? {
?????????? int temp_p;
????????? if (tree[x].parent != x)
????????? {
????????????? // 因為路徑壓縮,該結點的與根結點的關系要更新(因為前面合并時可能還沒來得及更新).
????????????? temp_p = tree[x].parent;
????????????? tree[x].parent = Find(tree[x].parent);
????????????? // x與根結點的關系更新(因為根結點變了),此時的temp_p為它原來子樹的根結點.
????????????? tree[x].relation = (tree[x].relation + tree[temp_p].relation) % 3;
????????? }
????????? return tree[x].parent;
?????? }
?????? void Merge(int a, int b, int p, int q, int d)
?????? {
????????? // 公式是找規律推出來的.
????????? tree[p].parent = q; // 這里的下標相同,都是tree[p].
????????? tree[p].relation = (tree[b].relation - tree[a].relation + 2 + d) % 3;
?????? }
?????? 而這種紀錄與根結點關系的方法,適用于幾乎所有的并查集判斷關系(至少我現在沒遇到過不適用的情況…可能是自己做的還太少了…),所以向大家強烈推薦~~
?????? 搞定了食物鏈這題,基本POJ上大部分基礎并查集題目就可以順秒了,這里僅列個題目編號:?POJ 1308 1611 1703 1984 1986(LCA Tarjan算法 + 并查集) 1988 2236 2492 2524。
?????? 下面來講解幾道稍微提高點的題目:
?????? POJ 1456 Supermarket
??? ???http://acm.pku.edu.cn/JudgeOnline/problem?id=1456
?????? 這道題貪心的思想很明顯,不過O(n^2)的復雜度明顯不行,我們可以用堆進行優化,這里講下并查集的優化方法(很巧妙)。我們把連續的被占用的區間看成一個集合(子樹),它的根結點為這個區間左邊第一個未被占用的區間。
先排序,然后每次判斷Find(b[i])是否大于0,大于0說明左邊還有未被占用的空間,則占用它,然后合并(b[i], Find(b[i]) – 1)即可。同樣這里我們規定只能左邊的子樹合并到右邊的子樹(想想為什么~~)。
?????? POJ 1733 Parity game
???? ??http://acm.pku.edu.cn/JudgeOnline/problem?id=1733
?????? 這題同樣用類似食物鏈的思想。
首先我們先離散化,因為原來的區間太大了(10^9),我們可以根據問題數目離散成(10^4)。我們要理解,這里的離散化并不影響最終的結果,因為區間里1的奇偶個數與區間的大小無關(這句話有點奇怪,可以忽略...),然后每次輸入a, b,我們把b++,如果他倆在一個集合內,那么區間[a, b]里1的個數相當于b.relation ^ a.relation,判斷對錯即可。如果不在一個集合內,合并集合(這里我們規定根結點小的子樹合并根結點大的,所以要根據不同情況推式子),修改子樹的根結點的狀態,子樹的其他結點狀態通過Find()函數來更新。
?????? hdu 3038 How Many Answers Are Wrong
???? ??http://acm.hdu.edu.cn/showproblem.php?pid=3038
?????? 上面那題的加強版,不需要離散化,因為區間的和與區間的大小有關(和上面的那句話對比下,同樣可以忽略之…),做法與上面那題差不多,只是式子變了,自己推推就搞定了。但這題還有個條件,就是每個點的值在[0, 100]之間,那么如果a, b不在一個子樹內,我們就合并,但在合并之前還要判斷合并后會不會使得區間的和不合法,如果會說明該合并是非法的,那么就不合并,同樣認為該句話是錯誤的。
?????? POJ 1417 True Liars(難)
???? ??http://acm.pku.edu.cn/JudgeOnline/problem?id=1417
?????? 并查集 + DP(或搜索)。
?????? 題目中告訴兩種人,一種只說真話,一種只說假話。然后告訴m條語句,問是否能判斷哪些人是只說真話的那類人。
?????? 其實并查集部分跟食物鏈還是相似,而且種類變少了一種,更容易了。我們可以通過并查集把有關系的一些人合并到一個集合內(具體方法參見食物鏈講解)。
?????? 現在的問題轉化為,有n個集合,每個集合都有a, b連個數字,現在要求n個集合中各跳出一個數(a或者b),使得他們之和等于n1(說真話的人數)。而這個用dp可以很好的解決,用f[i][j]表示到第i個集合和為j個的情況數,我們還用過pre[i][j]記錄當前選的是a還是b,用于后面判斷狀態。方程為f[i][j] = f[i – 1][j – a] + f[i – 1][j – b], j >= a, j >= b。如果最后f[n][n1] == 1說明是唯一的情況,輸出該情況,否則輸出 “no”(多解算no)
?????? 注意點 :?
?????? 1. 這題的m, n1, n2都有可能出現0,可以特殊處理,也可以一起處理。
?????? 2. 按上面的dp寫法,f[i][j]可能會很大,因為n可以達到三位數。其實我們關心的只是f[i][j] 等于0,等于1,大于1三種情況,所以當f[i][j] > 1時,我們都讓它等于2即可。
?????? POJ 2912 Rochambeau(難)
???? ??http://acm.pku.edu.cn/JudgeOnline/problem?id=2912
?????? Baidu Star 2006 Preliminary的題目,感覺出的很好,在并查集題目中算是較難的了。其實這題跟食物鏈完全一個磨子,同樣三類食物,同樣的互相制約關系。所以食物鏈代碼拿過來改都不需要改。但這題有個judge,他可以出任意手勢。于是我們的做法是,枚舉每個小孩為judge,判斷他為judge時在第幾句話出錯err[i](即到第幾句話能判斷該小孩不是judge)。
?????? 1. 如果只有1個小孩是judge時全部語句都是正確的,說明該小孩是judge,那么判斷的句子數即為其他小孩的err[i]的最大值。如果
?????? 2. 如果每個小孩的都不是judge(即都可以找到出錯的語句),那么就是impossible。
?????? 3. 多于1個小孩是judge時沒有找到出錯的語句,就是Can not determine。
??????? ZOJ 3261 Connections in Galaxy War
???? ??http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=3563
??????? nuaa 1087 聯通or不連通
???????http://acm.nuaa.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1087
??????? 兩題做法差不多,都是反過來的并查集題目,先對邊集排序,然后把要刪去的邊從二分在邊集中標記。然后并查集連接沒有標記的邊集,再按查詢反向做就可。第一題合并結點時按照題目要求的優先級合并即可。
???
?????? 這里介紹的并查集題目,主要都是處理些集合之間的關系(這是并查集的看家本領~~),至于并查集還有個用處就在求最小生成樹的Kruskal算法中,那個是圖論中求最小生成樹的問題(一般這個難點不在于并查集,它只是用于求最小生成樹的一種方法),就不在這里贅述了~~
czyuan原創,轉載請注明出處。
轉載于:https://www.cnblogs.com/isfight/archive/2011/10/24/2222904.html
總結
以上是生活随笔為你收集整理的POJ 并查集 题目汇总 ——czyuan原创(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 利用binlog增量备份,还
- 下一篇: MySql like 查询 变向写法(不