1. 程式人生 > >UVa 1252 - Twenty Questions(狀壓DP)

UVa 1252 - Twenty Questions(狀壓DP)

index 狀壓dp 就是 答案 option 所有 計算 int family

鏈接:

https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3693

題意:

有n(n≤128)個物體,m(m≤11)個特征。每個物體用一個m位01串表示,表示每個特征是具備還是不具備。
我在心裏想一個物體(一定是這n個物體之一),由你來猜。
你每次可以詢問一個特征,然後我會告訴你:我心裏的物體是否具備這個特征。
當你確定答案之後,就把答案告訴我(告知答案不算“詢問”)。
如果你采用最優策略,最少需要詢問幾次能保證猜到?
例如,有兩個物體:1100和0110,只要詢問特征1或者特征3,就能保證猜到。

分析:

為了敘述方便,設“心裏想的物體”為W。首先在讀入時把每個物體轉化為一個二進制整數。
不難發現,同一個特征不需要問兩遍,所以可以用一個集合k表示已經詢問的特征集。
在這個集合k中,有些特征是W所具備的,剩下的特征是W不具備的。
用集合c來表示“已確認物體W具備的特征集”,則c一定是k的子集。
設d(k,c)表示已經問了特征集k,其中已確認W所具備的特征集為c時,還需要詢問的最小次數。
如果下一次提問的對象是特征i(這就是“決策”),則詢問次數為:max{d(k+{i},c+{i}),d(k+{i},c)}+1。
考慮所有的i,取最小值即可。邊界條件為:如果只有一個物體滿足“具備集合c中的所有特征,


但不具備集合k-c中的所有特征”這一條件,則d(k,c)=0,因為無須進一步詢問,已經可以得到答案。
因為c為k的子集,所以狀態總數為3^m,時間復雜度為O(m*3^m)。
對於每個k和c,可以先把滿足該條件的物體個數統計出來,保存在amt[k][c],避免狀態轉移的時候重復計算。
統計amt[k][c]的方法是枚舉k和物體,時間復雜度為O(n*2^m),對於本題來說可以忽略不計。

代碼:

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int INF = 0x3f3f3f3f
; 6 const int UPM = 11; 7 const int UPN = 128; 8 int n, m, d[1<<UPM][1<<UPM], amt[1<<UPM][1<<UPM]; 9 char s[UPN+5][UPM+5]; 10 11 void init() { 12 int u = 1<<m; 13 for(int k = 0; k < u; k++) { 14 amt[k][0] = 0; 15 d[k][0] = INF; 16 for(int c = k; c; c = k&(c-1)) amt[k][c] = 0, d[k][c] = INF; 17 } 18 for(int t = 0; t < n; t++) { 19 int c = 0; 20 for(int i = 0; i < m; i++) if(s[t][i] == 1) c |= (1<<i); 21 for(int k = 0; k < u; k++) amt[k][k&c]++; 22 } 23 } 24 25 int dp(int k, int c) { 26 int& res = d[k][c]; 27 if(res != INF) return res; 28 if(amt[k][c] < 2) return res = 0; 29 for(int i = 0; i < m; i++) { 30 if(k&(1<<i)) continue; 31 int k2 = k|(1<<i), c2 = c|(1<<i); 32 int need = max(dp(k2,c), dp(k2,c2)); 33 res = min(res, need); 34 } 35 return res += 1; 36 } 37 38 int main() { 39 while(scanf("%d%d", &m, &n) && m) { 40 for(int i = 0; i < n; i++) scanf("%s", s[i]); 41 init(); 42 printf("%d\n", dp(0,0)); 43 } 44 return 0; 45 }

UVa 1252 - Twenty Questions(狀壓DP)