True Liars 【POJ - 1417】【種類並查集+0-1揹包】
阿新 • • 發佈:2018-12-25
題目連結
題目想要知道有P個好人(說真話的人)和Q個壞人(說假話的人),並且有N條資訊,代表A說B是好人(yes)、壞人(no),那麼,在保證答案唯一的情況下輸出這P個好人,並且最後的時候輸出“end”,否則,輸出“no”。坑點:答案唯一指的是最後你要確保它答案唯一,不是題目說答案唯一,如果答案不唯一,你得輸出“no”!!!
我的想法就是,既然有N條關係鏈,就是在變相的告訴我們了這P+Q個人的關係,對於一個說假話的人,他對應“yes”的人肯定是跟他一夥的,“no”就一定不是一夥的;同理,說真話的人。——這樣,我們可以確定每棵樹下的陣營關係。
然後,對於每棵樹,都有好人與壞人的數量,可以說是確定了兩個陣營的人的數量 ,我們既要選取了,若是選取陣營一的,就是把陣營二的人當作是壞人對待,若是選取陣營二的就是反之了。所以,這就是個揹包問題了。但這個揹包還不簡單,我們得記錄路徑和知曉答案的唯一性。
知曉答案的唯一性:我們可以通過到達最終狀態的可能性的多少來解,不如列寫一個dp[][],表示dp[訪問到第幾棵樹][已經有多少好人]如此向上推進。那麼,如果dp[樹的總數][P]不為1,就直接輸出“no”就是了,不為1,就代表了可能不止一個解,或者沒有解。
接下來,就是處理路徑,所謂的輸出好人:可以知道,好人有P個,並且,方案數唯一,就意味著如果此時的點的方案數不唯一,我們就不能選擇它,也是必須選擇方案固定的點,並且還要是能到達的點,對於一個點,我們有這樣的考慮,如果它的值是小於等於剩餘的可選好人數量,不能選,此時可以去考慮選擇它的對立面,就是對立陣營,並且,還必須滿足唯一方案。
#pragma comment(linker, "/STACK:102400000,102400000") #include <iostream> #include <cstdio> #include <cmath> #include <string> #include <cstring> #include <algorithm> #include <limits> #include <vector> #include <stack> #include <queue> #include <set> #include <map> #define lowbit(x) ( x&(-x) ) #define pi 3.141592653589793 #define e 2.718281828459045 #define efs 1e-7 using namespace std; typedef unsigned long long ull; typedef long long ll; const int maxN = 607; int N, tot, P, Q, root[maxN], num[maxN]; //P個好人,Q個壞人 int L, R, child[maxN][2][maxN], shu[maxN][2]; bool vis[maxN]; int gen[maxN], sum_gen, dp[maxN][maxN], ans[maxN], sum_ans; bool cmp(int e1, int e2) { return e1 < e2; } char op[10]; vector<int> vt[maxN][2]; int fid(int x) { if(x == root[x]) return x; int tmp = root[x]; root[x] = fid(root[x]); num[x] = (num[x] + num[tmp] + 2)%2; return root[x]; } void init() { for(int i=0; i<maxN; i++) root[i] = i; memset(num, 0, sizeof(num)); memset(shu, 0, sizeof(shu)); memset(vis, false, sizeof(vis)); memset(dp, 0, sizeof(dp)); tot = P + Q; sum_gen = sum_ans = 0; for(int i=1; i<maxN; i++) { for(int j=0; j<=1; j++) { vt[i][j].clear(); } } } int main() { while(scanf("%d%d%d", &N, &P, &Q) && (N || P || Q)) { init(); for(int i=1; i<=N; i++) { scanf("%d%d%s", &L, &R, op); if(P == Q) continue; //目的:唯一確定! if(L == R) continue; int u = fid(L), v = fid(R); if(op[0] == 'y') { if(u != v) { root[u] = v; num[u] = (num[R] - num[L] + 2)%2; } } else { if(u != v) { root[u] = v; num[u] = (num[R] - num[L] + 1 + 2)%2; //因為是no,所以一定在不同的陣營 } } } for(int i=1; i<=tot; i++) { int tmp = fid(i); if(!vis[tmp]) { vis[tmp] = true; gen[++sum_gen] = tmp; //處理這麼多個根,根離散化後的值 } child[tmp][num[i]][++shu[tmp][num[i]]] = i; //以tmp為根的,num[]為陣營的 vt[tmp][num[i]].push_back(i); } dp[0][0] = 1; for(int i=1; i<=sum_gen; i++) { for(int k=0; k<=1; k++) { for(int j=P; j>=shu[gen[i]][k]; j--) { dp[i][j] += dp[i-1][j-shu[gen[i]][k]]; } } } if(dp[sum_gen][P] != 1) { printf("no\n"); continue; } //沒有方案數,或方案數不唯一 for(int i=sum_gen; i>=1; i--) { if(P-shu[gen[i]][0]>=0 && Q-shu[gen[i]][1]>=0 && dp[i-1][P-shu[gen[i]][0]]) { for(int j=1; j<=shu[gen[i]][0]; j++) { ans[++sum_ans] = child[gen[i]][0][j]; } P -= shu[gen[i]][0]; Q -= shu[gen[i]][1]; } else if(P-shu[gen[i]][1]>=0 && Q-shu[gen[i]][0]>=0 && dp[i-1][P-shu[gen[i]][1]]) { for(int j=1; j<=shu[gen[i]][1]; j++) { ans[++sum_ans] = child[gen[i]][1][j]; } P -= shu[gen[i]][1]; Q -= shu[gen[i]][0]; } } sort(ans+1, ans+1+sum_ans, cmp); for(int i=1; i<=sum_ans; i++) printf("%d\n", ans[i]); printf("end\n"); } return 0; }