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 }