題目:http://poj.org/problem?id=1417
題意:輸入三個數m, p, q 分別表示接下來的輸入行數,天使數目,惡魔數目;
接下來m行輸入形如x, y, ch,ch為yes表示x說y是天使,ch為no表示x說y不是天使(x, y為天使,惡魔的編號,1<=x,y<=p+q);天使只說真話,惡魔只說假話;
如果不能確定所有天使的編號,輸出no,若能確定,輸出所有天使的編號,並且以end結尾;
注意:可能會有連續兩行一樣的輸入;還有,若x==y,x為天使;
思路:種類並查集+dp;
我們分析輸入的數據不難發現,對於輸入x, y, yes,假設x為天使,則y也為為天使,若x為惡魔,那么y也為惡魔,即x, y, 同為惡魔或者天使;
對於輸入x, y, no,同理可得x, y, 一者為天使一者為惡魔;即可得ch為yes時,x, y, 屬同種,ch為no時, x, y屬異種;
那么我們很容易就能想到種類並查集,rank[x]表示x與其父親節點的關系,rank[x]=0表示x與其父親節點屬於同類,rank[1]表示x與其父親節點屬於異類;通過並查集將能確定相對關系的編號放在一個集合里面,每個結合里面的編號可以分為兩部分,和根節點屬同種的的節點,以及和根節點屬於異種的節點;這樣並不能直接確定答案,我們確定了划分集合的個數以及每個集合里面和根節點同種的節點數目以及異節點的數目;從每個集合里面選擇一種節點,若所有選中的節點數目和為p的選擇方法唯一,那么我們能夠確定所有天使的編號,反之則不能;關於這個問題我們可以用dp完美解決;
事實上這個題目前面的並查集部分只是一個普通的種類並查集,這個記錄路徑的dp才是本題解的精妙部分;我們先用一個tot變量來存儲集合個數(對於x==y的情況,我們讓x單獨為一個集合),並且用gg數組來標記所有編號屬於的集合;用tag數組存儲每個集合兩種種類的數目;dp[i][j]表示到第i個集合選擇種類的和為j的方法總數,即dp[tot][p]==1時能確定答案;對於dp過程中的每個選擇我們用jj數組記錄,然后反推選擇路徑用cc數組記錄路徑就ok啦~
代碼:
1 #include <iostream>
2 #include <stdio.h>
3 #include <string.h>
4 #define MAXN 600
5 using namespace std; 6
7 int pre[MAXN], rank[MAXN], gg[MAXN], jj[MAXN][MAXN], tag[MAXN][2], dp[MAXN][MAXN], cc[MAXN][2]; 8
9 int find(int x){ 10 if(x!=pre[x]){ 11 int px=find(pre[x]); 12 rank[x]^=rank[pre[x]]; 13 pre[x]=px; 14 } 15 return pre[x]; 16 } 17
18 void jion(int x, int y, int d){ 19 int px=find(x); 20 int py=find(y); 21 if(px!=py){ 22 pre[py]=px; 23 rank[py]=rank[x]^rank[y]^d; 24 } 25 } 26
27 int main(void){ 28 int m, p, q; 29 while(scanf("%d%d%d", &m, &p, &q)){ 30 if(m+p+q==0){ 31 break; 32 } 33 for(int i=1; i<=p+q; i++){ //**初始話
34 rank[i]=0; 35 pre[i]=i; 36 } 37 while(m--){ 38 int x, y, d=1; 39 char ch[5]; 40 scanf("%d%d%s", &x, &y, ch); 41 if(ch[0]=='y'){ 42 d=0; 43 } 44 jion(x, y, d); 45 } 46 memset(gg, 0, sizeof(gg)); //**gg存儲集合個數並且給他們編號
47 memset(jj, 0, sizeof(jj)); 48 memset(tag, 0, sizeof(tag)); 49 memset(dp, 0, sizeof(dp)); 50 memset(cc, 0, sizeof(cc)); 51 int tot=0; 52 for(int i=1; i<=p+q; i++){ //**統計集合個數並且編號
53 if(find(i)==i){ 54 gg[i]=++tot; 55 } 56 } 57 for(int i=1; i<=p+q; i++){ //**分別統計每個集合兩種類的數目並存儲到tag中
58 tag[gg[find(i)]][rank[i]]++; 59 } 60 dp[0][0]=1; 61 for(int i=1; i<=tot; i++){ 62 for(int j=0; j<=p+q; j++){ //**dp[i][j]存儲到第i個集合選擇種類和為j的方法數
63 if(j-tag[i][0]>=0&&dp[i-1][j-tag[i][0]]){ 64 dp[i][j]+=dp[i-1][j-tag[i][0]]; 65 jj[i][j]=tag[i][0]; //**jj數組記錄路徑,即選的是1還是0
66 } 67 if(j-tag[i][1]>=0&&dp[i-1][j-tag[i][1]]){ 68 dp[i][j]+=dp[i-1][j-tag[i][1]]; 69 jj[i][j]=tag[i][1]; 70 } 71 } 72 } 73 if(dp[tot][p]!=1){ 74 printf("no\n"); 75 }else{ 76 for(int i=tot,j=p; j>0&&i>0; i--){ //**標記路徑
77 if(jj[i][j]==tag[i][0]){ 78 cc[i][0]=1; 79 }else{ 80 cc[i][1]=1; 81 } 82 j-=jj[i][j]; 83 } 84 for(int i=1; i<=p+q; i++){ 85 if(cc[gg[find(i)]][rank[i]]){ 86 printf("%d\n", i); 87 } 88 } 89 printf("end\n"); 90 } 91 } 92 return 0; 93 }