【轉載】帶權並查集經典例題

  1 #include <cstdio>
  2 #include <cstdlib>
  3 #include <cstring>
  4 #include <iostream>
  5 //#define INPUT
  6 /**
  7     Problem:1182 - 食物鏈,NOI2001
  8     Begin Time:4th/Mar/2012 1:00 p.m.
  9     End Time:4th/Mar/2012 6:47 p.m.
 10     Cost Time:兩天多,看的別人的解題報告AC的
 11     Reference:http://apps.hi.baidu.com/share/detail/16059767
 12     測試數據:
 13     http://poj.org/showmessage?message_id=93058
 14     輸出:
 15     上方有
 16     教訓:
 17         WA一次,沒搞清楚先更新父節點relation還是更新當前節點relation的關係!!!
 18         (在最後那條犯錯誤了!)
 19     思路:
 20     老子決心要寫一個,關於這道題的,最詳細的解題報告。
 21     本題思路是帶權並查集,我們從最開始講起。
 22     Part I  - 權值(relation)的確定。
 23     我們根據題意,森林中有3種動物。A吃B,B吃C,C吃A。
 24     我們還要使用並查集,那麼,我們就以動物之間的關係來作爲並查集每個節點的
 25     權值。
 26     注意,我們不知道所給的動物(題目說了,輸入只給編號)所屬的種類。
 27     所以,我們可以用動物之間“相對”的關係來確定一個並查集。
 28     0 - 這個節點與它的父節點是同類
 29     1 - 這個節點被它的父節點吃
 30     2 - 這個節點吃它的父節點。
 31     注意,這個0,1,2所代表的意義不是隨便制定的,我們看題目中的要求。
 32     說話的時候,第一個數字(下文中,設爲d)指定了後面兩種動物的關係:
 33     1 - X與Y同類
 34     2 - X吃Y
 35     我們注意到,當 d = 1的時候,( d - 1 ) = 0,也就是我們制定的意義
 36                 當 d = 2的時候,( d - 1 ) = 1,代表Y被X吃,也是我們指定的意義。
 37     所以,這個0,1,2不是隨便選的
 38     Part II - 路徑壓縮,以及節點間關係確定
 39     確定了權值之後,我們要確定有關的操作。
 40     我們把所有的動物全初始化。
 41     struct Animal
 42     {
 43         int num; //該節點(node)的編號
 44         int parent; //該node的父親
 45         int relation; //該node與父節點的關係,0同類,1被父節點吃,2吃父節點
 46     }; Animal ani[50010];
 47         初始化爲
 48         For i = 0 to N do
 49             ani[i].num = i;
 50             ani[i].parent = i;
 51             ani[i].relation = 0 ; //自己和自己是同類
 52         End For
 53         (1)路徑壓縮時的節點算法
 54         我們設A,B,C動物集合如下:(爲了以後便於舉例)
 55         A = { 1 , 2 , 3 ,4 ,5 }
 56         B = { 6 , 7 , 8 ,9 ,10}
 57         C = { 11, 12, 13,14,15}
 58         假如我們已經有了一個集合,分別有3個元素
 59         SET1 = {1,2},我們規定集合中第一個元素爲並查集的“代表”
 60         假如現在有語句:
 61         2 2 6
 62         這是一句真話
 63         2是6的父親
 64          ani[6].parent = 2;
 65          ani[6].relation = 1;
 66         那麼,6和1的關係如何呢?
 67          ani[2].parent = 1;
 68          ani[2].relation = 0;
 69         我們可以發現6與2的關係是 1.
 70         通過窮舉我們可以發現
 71         ani[now].parent = ani[ani[now].parent].parent;
 72         ani[now].relation = ( ani[now].relation + ani[now.parent].relation ) % 3;
 73         這個路徑壓縮算法是正確的
 74         關於這個路徑壓縮算法,還有一點需要注意的地方,我們一會再談
 75         注意,根據當前節點的relation和當前節點父節點的relation推出
 76         當前節點與其父節點的父節點的relation這個公式十分重要!!
 77         它推不出來下面都理解不了!!自己用窮舉法推一下:
 78         好吧,爲了方便伸手黨,我給出窮舉過程
 79                 i      j
 80         爺爺  父親  兒子  兒子與爺爺
 81                0      0       (i + j)%3 = 0
 82                0      1       (i + j)%3 = 1
 83                0      2       (i + j)%3 = 2
 84                1      0       (i + j)%3 = 1
 85                1      1       (i + j)%3 = 2
 86                1      2       (i + j)%3 = 0
 87                2      0       (i + j)%3 = 2
 88                2      1       (i + j)%3 = 0
 89                2      2       (i + j)%3 = 1
 90         嗯,這樣可以看到,( 兒子relation + 父親relation ) % 3 = 兒子對爺爺的relation
 91         這就是路徑壓縮的節點算法
 92         (2) 集合間關係的確定
 93         在初始化的時候,我們看到,每個集合都是一個元素,就是他本身。
 94         這時候,每個集合都是自洽的(集合中每個元素都不違反題目的規定)
 95         注意,我們使用並查集的目的就是儘量的把路徑壓縮,使之高度儘量矮
 96         假設我們已經有一個集合
 97         set1 = {1,2,7,10}
 98         set2 = {11,4,8,13},每個編號所屬的物種見上文
 99         set3 = {12,5,4,9}
100         現在有一句話
101         2 13 2
102         這是一句真話,X = 13,Y = 2
103         我們要把這兩個集合合併成一個集合。
104         直接
105         int a = findParent(ani[X]);
106         int b = findParent(ani[Y]);
107         ani[b].parent = a;
108         就是把Y所在集合的根節點的父親設置成X所在集合的根節點。
109         但是,但是!!!!
110         Y所在集合的根結點與X所在集合的根節點的關係!!!要怎麼確定呢?
111         我們設X,Y集合都是路徑壓縮過的,高度只有2層
112         我們先給出計算的公式
113         ani[b].relation = ( 3 - ani[Y].relation + ( d - 1 ) + ani[X].relation) % 3;
114         這個公式,是分三部分,這麼推出來的
115         第一部分,好理解的一部分:
116         ( d - 1 ) :這是X和Y之間的relation,X是Y的父節點時,Y的relation就是這個
117         3 - ani[Y].relation = 根據Y與根節點的關係,逆推根節點與Y的關係
118         這部分也是窮舉法推出來的,我們舉例:
119         j
120         子         父相對於子的relation(即假如子是父的父節點,那麼父的relation應該是什麼,因爲父現在是根節點,所以父.relation = 0,我們只能根據父的子節點反推子跟父節點的關係)
121          0             ( 3 - 0 ) % 3 = 0
122          1(父吃子)   ( 3 - 1 ) % 3 = 2 //父吃子
123          2(子吃父)    ( 3 - 2 ) % 3 = 1 //子吃父,一樣的哦親
124         ——————————————————————————————————————————————————————
125         我們的過程是這樣的:
126         把ani[Y],先連接到ani[X]上,再把ani[Y]的根節點移動到ani[X]上,最後,把ani[Y]的根節點移動到ani[X]的根節點上,這樣算relation的
127         還記得麼,如果我們有一個集合,壓縮路徑的時候父子關係是這麼確定的
128         ani[爺爺].relation = ( ani[父親].relation + ani[兒子].relation ) % 3
129         我們已知道,( d - 1 )就是X與Y的relation了
130         而 (3 - ani[Y].relation)就是 以Y爲根節點時,他的父親的relation
131         那麼
132         我們假設把Y接到X上,也就說,現在X是Y的父親,Y原來的根節點現在是Y的兒子
133           Y的relation   +     ani[Y]根節點相對於ani[Y]的relation
134         ( ( d - 1 )         +    ( 3 - ani[Y].relation) ) % 3
135         就是ani[Y]的父親節點與ani[X]的relation了!
136         那麼,不難得到,ani[Y]的根節點與ani[X]根節點的關係是:
137         ( ( d - 1 ) + ( 3 - ani[Y].relation) + ani[X].relation ) % 3 ->應用了同餘定理
138         注意,這個當所有集合都是初始化狀態的時候也適用哦
139         還是以最開頭我們給的三個集合(分別代表三個物種)爲例
140         2 1 6
141         帶入公式
142         ani[6].relation = ( ( 2 - 1 ) + ( 3 - 0 ) + 0 ) % 3 = 1
143         也就是,6被1吃
144     Part III - 算法正確性的證明
145         首先,兩個自洽的集合,合併以後仍然是自洽的
146         這個不難想吧,數學上有個什麼對稱性定理跟他很像的。
147         如果理解不了,就這麼想!!
148         當set1和set2合併之後,set2的根節點得到了自己關於set1根節點的
149         正確relation值,變成了set1根節點的兒子,那麼
150         set2的所有兒子只要用
151         ( ani[X].relation + ani[Y].relation ) % 3就能得到自己正確的relation值了
152         所以說,針對不在同一集合的兩個元素的話,除非違背了(2)和(3),否則永遠是真的
153         (無論這句話說的是什麼,我們都可以根據所給X,Y推出兩個子節點之間應有的關係,這個關係一確定,所有兒子的關係都可以確定)
154         其實所有的不同集合到最後都會被合併成一個集合的。
155         我們只要在一個集合中找那些假話就可以了。
156         首先,如何判斷
157         1 X Y是不是假話。//此時 d = 1
158         if ( X 和 Y 不在同一集合)
159             Union(x,y,xroot,yroot,d)
160         else
161             if x.relation != y.relation  ->假話
162         其次,如何判斷
163         2 X Y是不是假話 //此時d = 2
164         if ( X 和 Y 不在同一集合)
165             Union(x,y,xroot,yroot,d)
166         else
167             (ani[y].relation + 3 - ani[x].relation ) % 3 != 1 ->假話
168         這個公式是這麼來的:
169         3 - ani[x].relation得到了根節點關於x的relation
170         ani[y] + 3 - ani[x].relation得到了y關於x的relation
171         所以,只要y關於x的relation不是1,就是y不被x吃的話,這句話肯定是假話!
172 
173         (2)路徑壓縮要特別注意的一點(錯在這裏,要檢討自己)
174             路徑壓縮的時候,記得要
175             先findParent,再給當前節點的relation賦值。
176             否則有可能因爲當前節點的父節點的relation不正確而導致錯的稀里嘩啦。
177             例子:
178             set1 = {1,2,7,10}
179             set2 = {3,4,8,11}
180             set3 = {12,5,14,9}
181             Union(1,3,1,3,1)
182             Union(3,12,3,12,2)
183             1 5 1
184             算5的relation
185             如果不先更新parent的relation,算出來應該是
186             ( 3 - 0 + 0 + 1 ) % 3 = 1,5被1吃,顯然不對
187             這裏面,+ 0的那個0是指根節點 12 的relation(未更新,這裏的0是指12與11的relation)
188             如果更新完了的話,應該是
189             ( 3 - 0 + 2 + 1 ) % 3 = 0 ,5與1是同一物種,對了
190             這裏面的 2 是更新節點12的relation(12與1的relation)
191     後記:
192         關於這道題,我在網上搜索了許多解題報告,但是都閃爍其詞,大概大家都不想
193         把自己辛辛苦苦推出來的公式寫到網上供別人學習來節省時間吧。
194         我覺得這麼做不好,對初學者容易產生不良影響,ACM如果只是一個小衆化的圈子,那
195         豈不是太沒意思了。
196         於是我就把我自己總結的這道題的經驗放了出來,希望可以幫得到大家
197         自己總結的,對錯也不知道,但是起碼是“自洽”的,^ ^
198         感謝那篇博文的博主,也感謝gzm,lqy兩位學長的指導。
199         c0de4fun
200 */
201 using namespace std;
202 const int c0de4fun = 50010;//動物個數的最大值
203 ///指明父節點與自己的關係,0同類,1被吃,2吃父
204 const int SAME = 0;
205 const int ENEMY = 1;
206 const int FOOD = 2;
207 struct Animal
208 {
209     int parent;
210     int num;
211     int relation;
212 };
213 Animal ani[c0de4fun];
214 long ans;
215 int findParent(Animal* node)
216 {
217     ///Wrong Answer 因爲這個函數寫錯了
218     ///這個函數得是“自洽的”
219     ///就是說,得保證每個元素的父親的relation是對的
220     ///再算自己的relation
221     ///因爲自己的relation和父親的relation有關
222     ///這就是爲什麼要先findParent再relation更新的原因
223     int tmp;
224     if( node->parent == node->num )
225         return node->parent;
226     tmp = node->parent;
227 #ifdef DBG
228     printf("Animal %d s Parent is %d\n",node->num,node->parent);
229 #endif
230    // node->relation = ( ani[node->parent].relation + node->relation ) % 3;
231     node->parent = findParent(&ani[node->parent]);
232     node->relation = ( ani[tmp].relation + node->relation ) % 3;
233     return node->parent;
234 }
235 void Union(int x,int y,int a,int b,int d)
236 {
237     ani[b].parent = a;
238     ///rootY.parent = rootX.parent;
239     ani[b].relation =( (3 - ani[y].relation) + (d - 1) + ani[x].relation) % 3;
240 }
241  
242 void init_Animal(int n)
243 {
244     for(int i = 1 ; i <= n ; i++)
245     {
246         ani[i].num = i;
247         ani[i].parent = i;
248         ani[i].relation = SAME;
249     }
250 }
251 int main(int argc,char* argv[])
252 {
253     int N,K;
254     int d,X,Y;
255 #ifdef INPUT
256     freopen("b:\\acm\\poj1182\\input.txt","r",stdin);
257 #endif
258     scanf("%d%d",&N,&K);
259     init_Animal(N);
260     for(int i = 0 ; i < K ; i++)
261     {
262         scanf("%d%d%d",&d,&X,&Y);
263         if( X > N || Y > N)
264             ans++;
265         else
266         {
267             if(d == 2 && X == Y)
268                 ans++;
269             else
270             {
271                 int a = findParent(&ani[X]);
272                 int b = findParent(&ani[Y]);
273                 if ( a != b )
274                 {
275                     ///x,y不在同一集合中
276                     Union(X,Y,a,b,d);
277                 }
278                 else
279                 {
280                     switch(d)
281                     {
282                         case 1:
283                             if(ani[X].relation != ani[Y].relation)
284                                 ans++;
285                             break;
286                         case 2:
287                             if(((ani[Y].relation + 3 - ani[X].relation) % 3 ) != 1)
288                                 ans++;
289                             break;
290                     }
291                 }
292             }
293         }
294     }
295     printf("%d\n",ans);
296     return 0;
297 }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章