POJ:1182 食物链(带权并查集)
生活随笔
收集整理的這篇文章主要介紹了
POJ:1182 食物链(带权并查集)
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
http://poj.org/problem?id=1182
Description
動(dòng)物王國(guó)中有三類動(dòng)物A,B,C,這三類動(dòng)物的食物鏈構(gòu)成了有趣的環(huán)形。A吃B, B吃C,C吃A。現(xiàn)有N個(gè)動(dòng)物,以1-N編號(hào)。每個(gè)動(dòng)物都是A,B,C中的一種,但是我們并不知道它到底是哪一種。
有人用兩種說(shuō)法對(duì)這N個(gè)動(dòng)物所構(gòu)成的食物鏈關(guān)系進(jìn)行描述:
第一種說(shuō)法是"1 X Y",表示X和Y是同類。
第二種說(shuō)法是"2 X Y",表示X吃Y。
此人對(duì)N個(gè)動(dòng)物,用上述兩種說(shuō)法,一句接一句地說(shuō)出K句話,這K句話有的是真的,有的是假的。當(dāng)一句話滿足下列三條之一時(shí),這句話就是假話,否則就是真話。
1) 當(dāng)前的話與前面的某些真的話沖突,就是假話;
2) 當(dāng)前的話中X或Y比N大,就是假話;
3) 當(dāng)前的話表示X吃X,就是假話。
你的任務(wù)是根據(jù)給定的N(1 <= N <= 50,000)和K句話(0 <= K <= 100,000),輸出假話的總數(shù)。
Input
第一行是兩個(gè)整數(shù)N和K,以一個(gè)空格分隔。以下K行每行是三個(gè)正整數(shù) D,X,Y,兩數(shù)之間用一個(gè)空格隔開(kāi),其中D表示說(shuō)法的種類。
若D=1,則表示X和Y是同類。
若D=2,則表示X吃Y。
Output
只有一個(gè)整數(shù),表示假話的數(shù)目。Sample Input
100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5Sample Output
3//一個(gè)介紹并查集的博客http://ke.baidu.com/view/f11c8fbd551810a6f524866f.html
//我主要參考了這個(gè)博客//http://blog.csdn.net/wmn_wmn/article/details/7416370
題解:
主要不是做這個(gè)題,而是用到的算法,先說(shuō)下這題的坑,輸入while(scanf("%d%",&n,&k)!=EOF)就WA,只有一組
測(cè)試數(shù)據(jù),另外不能用cin,因?yàn)閗的范圍為100000;超時(shí)。現(xiàn)在還不怎么懂為什么可以用向量的方法做,只能暫且記下來(lái)了。
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> using namespace std; int n,k,sum; struct node {int fa;int relation; } q[50001]; void init() {for(int i=1; i<=n; i++){q[i].fa=i;q[i].relation=0;}sum=0; } int findx(int x) {int temp;if(x==q[x].fa){return x;}temp=q[x].fa;q[x].fa=findx(temp);//雖然和以前的寫(xiě)法不同,但路徑壓縮的思想是一樣的q[x].relation=(q[temp].relation+q[x].relation)%3;return q[x].fa; } int main() {int d,x,y;scanf("%d%d",&n,&k);init();while(k--){scanf("%d%d%d",&d,&x,&y);if(x>n||y>n){sum++;continue;}if(d==2&&x==y){sum++;continue;}int fx,fy;fx=findx(x);fy=findx(y);if(fx!=fy){q[fy].fa=fx;q[fy].relation=(q[x].relation+d-1+3-q[y].relation)%3;}else{if(d==1&&q[x].relation!=q[y].relation){sum++;continue;}else if(d==2&&(3-q[x].relation+q[y].relation)%3!=1){sum++;continue;}}}printf("%d\n",sum);return 0; }?大神總結(jié)的算法,果斷粘貼
解題思路:這道題是并查集題目中的經(jīng)典。。。而且比普通并查集提高了一個(gè)檔次,下面在基礎(chǔ)并查集的前提上講解并查集的真正用法。基礎(chǔ)回顧:find()函數(shù)找根結(jié)點(diǎn)的兩種寫(xiě)法如下:第一種遞歸:int find(int x) { return x == pre[x] ? x : find(pre[x]); } 第二種:int find(int x) { int root, temp; root = x; while(root != pre[root]) root = pre[root]; while(x != root) { temp = pre[x]; pre[temp] = root; x = temp; } return root; } 上面2種是最基本的查找操作。下面我們通過(guò)這道題來(lái)講解一下并查集的深層次應(yīng)用。輸入:動(dòng)物個(gè)數(shù)n以及k句話,接著輸入k行,每一行形式為:d x y,在輸入時(shí)可以先判斷題目所說(shuō)的條件2和3,即:1>若(x>n||y>n):即當(dāng)前的話中x或y比n大,則假話數(shù)目sum加1.2>若(x==2&&x==y):即當(dāng)前的話表示x吃x,則假話數(shù)目sum加1.而不屬于這兩種情況外的話語(yǔ)要利用并查集進(jìn)行判斷當(dāng)前的話是否與此前已經(jīng)說(shuō)過(guò)的話相沖突.struct node { int parent; //p[i].parent表示節(jié)點(diǎn)i的父節(jié)點(diǎn) int relation; //p[i].relation表示節(jié)點(diǎn)i與其父節(jié)點(diǎn)(即p[i].parent)的關(guān)系 }p[50010]; 此處relation有三種取值(假設(shè)節(jié)點(diǎn)x的父節(jié)點(diǎn)為rootx,即p[x].parent=rootx):p[x].relation=0 ……表示節(jié)點(diǎn)x與其父節(jié)點(diǎn)rootx的關(guān)系是:同類p[x].relation=1 ……表示節(jié)點(diǎn)x與其父節(jié)點(diǎn)rootx的關(guān)系是:被根結(jié)點(diǎn)吃p[x].relation=2 ……表示節(jié)點(diǎn)x與其父節(jié)點(diǎn)rootx的關(guān)系是:吃根結(jié)點(diǎn)初始化函數(shù)為:void init(int n) { int i; for(i = 1;i <= n; ++i) { p[i].parent = i; //初始時(shí)集合編號(hào)就設(shè)置為自身 p[i].relation = 0; //因?yàn)閜[i].parent=i,即節(jié)點(diǎn)i的父親節(jié)點(diǎn)就是自身,所以此時(shí)節(jié)點(diǎn)i與其父親節(jié)點(diǎn)的關(guān)系為同類(即p[i].relation=0) } } 下面詳細(xì)講解并查集的兩個(gè)重要操作:查找和合并.查找操作:在查找時(shí)因?yàn)楣?jié)點(diǎn)不僅有父親節(jié)點(diǎn)域,而且還有表示節(jié)點(diǎn)與其父親節(jié)點(diǎn)的關(guān)系域,查找過(guò)程中對(duì)父親節(jié)點(diǎn)域的處理和簡(jiǎn)單的并查集處理一樣,即在查找過(guò)程中同時(shí)實(shí)現(xiàn)路徑壓縮,但正是由于路徑壓縮,使得表示節(jié)點(diǎn)與其父親節(jié)點(diǎn)的關(guān)系域發(fā)生了變化,所以在路徑壓縮過(guò)程中節(jié)點(diǎn)和其對(duì)應(yīng)的父節(jié)點(diǎn)的關(guān)系域發(fā)生了變化(因?yàn)槁窂綁嚎s之前節(jié)點(diǎn)x的父親節(jié)點(diǎn)為rootx的話,那么在路徑壓縮之后節(jié)點(diǎn)x的父親節(jié)點(diǎn)就變?yōu)榱斯?jié)點(diǎn)rootx的父親節(jié)點(diǎn)rootxx,所以此時(shí)p[x].relation存儲(chǔ)的應(yīng)該是節(jié)點(diǎn)x與現(xiàn)在父親節(jié)點(diǎn)rootxx的關(guān)系),此處可以畫(huà)圖理解一下:很明顯查找之前節(jié)點(diǎn)x的父親節(jié)點(diǎn)為rootx,假設(shè)此時(shí)p[x].relation=1(即表示x的父親節(jié)點(diǎn)rootx吃x)且p[rootx].relation=0(即表示rootx和其父親節(jié)點(diǎn)rootxx是同類),由這兩個(gè)關(guān)系可以推出rootxx吃x,而合并以后節(jié)點(diǎn)x的父親節(jié)點(diǎn)為rootxx(實(shí)現(xiàn)了路徑壓縮),且節(jié)點(diǎn)x的父親節(jié)點(diǎn)rootxx吃x,即查找之后p[x].relation=1。合并操作:在將元素x與y所在的集合合并時(shí),假設(shè)元素x所在的集合編號(hào)為rootx,元素y所在的集合編號(hào)為rooty,合并時(shí)直接將集合rooty掛到集合rootx上,即p[rooty].parent=rootx,此時(shí)原來(lái)集合rooty中的根節(jié)點(diǎn)rooty的關(guān)系域也應(yīng)隨之發(fā)生變化,因?yàn)楹喜⒅皉ooty的父親節(jié)點(diǎn)就是其自身,故此時(shí)p[rooty].relation=0,而合并之后rooty的父親節(jié)點(diǎn)為rootx,所以此時(shí)需判斷rootx與rooty的關(guān)系,即更新p[rooty]的值,同理畫(huà)圖理解:此時(shí)假設(shè)假設(shè)p[x].relation=0(即x與rootx的關(guān)系是同類),p[y].relation=1(即rooty吃y),則有:1>輸入d=1時(shí),即輸入的x和y是同類,則有上述關(guān)系可以推出rooty吃rootx,即p[rooty].relation=2;2>輸入d=2時(shí),即輸入的x吃y,則有上述關(guān)系可以推出rooty與rootx是同類(因?yàn)閞ooty吃y,x吃y,則rooty與x是同類,又rootx與x是同類),即p[rooty].relation=0;當(dāng)然,這只是一種可能,其它的可能情況和上面一樣分析。當(dāng)元素x與元素y在同一集合時(shí),則不需要合并,因?yàn)榇藭r(shí)x與y的父親節(jié)點(diǎn)相同,可以分情況討論:1>d=1時(shí),即x與y是同類時(shí),此時(shí)要滿足這要求,則必須滿足p[x].relation=p[y].relation,這很容易推出來(lái).2>d=2時(shí),即表示x吃y,此時(shí)要滿足這要求,則也必須滿足一定的條件,如x和root是同類(即p[x].relation=0),此時(shí)要滿足x吃y,則必須滿足root吃y,即p[y].relation=1,可以像上面一樣畫(huà)圖來(lái)幫助理解.關(guān)系域更新: 當(dāng)然,這道題理解到這里思路已經(jīng)基本明確了,剩下的就是如何實(shí)現(xiàn),在實(shí)現(xiàn)過(guò)程中,我們發(fā)現(xiàn),更新關(guān)系域是一個(gè)很頭疼的操作,網(wǎng)上各種分析都有,但是都是直接給出個(gè)公式,至于怎么推出來(lái)的都是一筆帶過(guò),讓我著實(shí)頭疼了很久,經(jīng)過(guò)不斷的看discuss,終于明白了更新操作是通過(guò)什么來(lái)實(shí)現(xiàn)的。下面講解一下仔細(xì)再想想,rootx-x 、x-y、y-rooty,是不是很像向量形式?于是我們可以大膽的從向量入手:tx ty| |x ~ y對(duì)于集合里的任意兩個(gè)元素x,y而言,它們之間必定存在著某種聯(lián)系,因?yàn)椴⒉榧械脑鼐怯新?lián)系的(這點(diǎn)是并查集的實(shí)質(zhì),要深刻理解),否則也不會(huì)被合并到當(dāng)前集合中。那么我們就把這2個(gè)元素之間的關(guān)系量轉(zhuǎn)化為一個(gè)偏移量(大牛不愧為大牛!~YM)。由上面可知: x->y 偏移量0時(shí) x和y同類x->y 偏移量1時(shí) x被y吃x->y 偏移量2時(shí) x吃y有了這個(gè)假設(shè),我們就可以在并查集中完成任意兩個(gè)元素之間的關(guān)系轉(zhuǎn)換了。不妨繼續(xù)假設(shè),x的當(dāng)前集合根節(jié)點(diǎn)rootx,y的當(dāng)前集合根節(jié)點(diǎn)rooty,x->y的偏移值為d-1(題中給出的詢問(wèn)已知條件)(1)如果rootx和rooty不相同,那么我們把rooty合并到rootx上,并且更新relation關(guān)系域的值(注意:p[i].relation表示i的根結(jié)點(diǎn)到i的偏移量!!!!(向量方向性一定不能搞錯(cuò)))此時(shí) rootx->rooty = rootx->x + x->y + y->rooty,這一步就是大牛獨(dú)創(chuàng)的向量思維模式上式進(jìn)一步轉(zhuǎn)化為:rootx->rooty = (relation[x]+d-1+3-relation[y])%3 = relation[rooty],(模3是保證偏移量取值始終在[0,2]間)(2)如果rootx和rooty相同(即x和y在已經(jīng)在一個(gè)集合中,不需要合并操作了,根結(jié)點(diǎn)相同),那么我們就驗(yàn)證x->y之間的偏移量是否與題中給出的d-1一致此時(shí) x->y = x->rootx + rootx->y上式進(jìn)一步轉(zhuǎn)化為:x->y = (3-relation[x]+relation[y])%3,若一致則為真,否則為假。分析到這里,這道題已經(jīng)從思想過(guò)渡到實(shí)現(xiàn)了。剩下的就是一些細(xì)節(jié)問(wèn)題,自己處理一下就好了。PS:做完這題,就可以去秒了大部分基礎(chǔ)的并查集了,嘿嘿大笑代碼如下:#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define N 50010 struct node { int pre; int relation; }; node p[N]; int find(int x) //查找根結(jié)點(diǎn) { int temp; if(x == p[x].pre) return x; temp = p[x].pre; //路徑壓縮 p[x].pre = find(temp); p[x].relation = (p[x].relation + p[temp].relation) % 3; //關(guān)系域更新 return p[x].pre; //根結(jié)點(diǎn) } int main() { int n, k; int ope, a, b; int root1, root2; int sum = 0; //假話數(shù)量 scanf("%d%d", &n, &k); for(int i = 1; i <= n; ++i) //初始化 { p[i].pre = i; p[i].relation = 0; } for(int i = 1; i <= k; ++i) { scanf("%d%d%d", &ope, &a, &b); if(a > n || b > n) //條件2 { sum++; continue; } if(ope == 2 && a == b) //條件3 { sum++; continue; } root1 = find(a); root2 = find(b); if(root1 != root2) // 合并 { p[root2].pre = root1; p[root2].relation = (3 + (ope - 1) +p[a].relation - p[b].relation) % 3; } else { if(ope == 1 && p[a].relation != p[b].relation) { sum++; continue; } if(ope == 2 && ((3 - p[a].relation + p[b].relation) % 3 != ope - 1)) { sum++; continue;} } } printf("%d\n", sum); return 0; }
?
總結(jié)
以上是生活随笔為你收集整理的POJ:1182 食物链(带权并查集)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 加油站问题【贪心】
- 下一篇: Verilog学习笔记4:关于5M40Z