UVA1252 Twenty Questions
vjudge传送门
题面:有\(n(n \leqslant 128)\)个物体,\(m(m \leqslant 11)\)个特征。每个物体用一个\(m\)位01串表示,表示每个特征是否具备。我在心里想一个物体\(W\)(\(n\)个之一),你来猜。每次可以询问一个特征,然后我会告诉你:\(x\)是否具备这个特征。当你确定之后,就把答案告诉我(告知答案不算“询问”)。如果采用最优策略,最少需要询问几次能保证猜到?例如,有两个物体:1100和0110,只要询问特征1或者特征3,就能1次猜到。
突然感觉有点博弈论那味儿了。
但这只是一道状压dp题。
状态我觉得不太好想,可能是这题的思维不太一样吧。
令\(dp[Q][A]\)表示当前问的问题集合是\(Q\),\(Q\)中是否具备的集合为\(A\)(\(Q,A\)都是一个二进制压缩的状态,\(A\)是\(Q\)的子集)时,最少还需几次能保证猜到。
比如Q=10011010,A=00010010,那么能确定的特征序列就是0XX10X1X,所以我们只要看这个特征序列在\(n\)个物体中是否是独有的,如果是,就返回,否则继续dp。
那么我们应该从Q=11111111的状态开始向前dp,用循环写起来比较费劲,所以为了简化代码,采用记忆化搜索的方式。
现在只要知道怎么判断特征序列是否是独有的就好了:其实只要\(Q \& a[i]=A\)就行了。
我们分情况考虑:
1.Q是0的位置:表示没有问到,那按位与后自然也是0,而A的这一位也是0,相等。
2.Q是1的位置:表示问到了,因为我们要比较a[i]这一位是否与A相等,而按位与在这一位的结果只取决于a[i],那么自然就符合要求。
时间复杂度是枚举子集的复杂度:\(O(n3 ^ m)\)。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<queue>
#include<assert.h>
#include<ctime>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 130;
const int maxm = 12;
In ll read()
{
ll ans = 0;
char ch = getchar(), las = ' ';
while(!isdigit(ch)) las = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(las == '-') ans = -ans;
return ans;
}
In void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
#endif
}
int n, m, a[maxn];
char s[maxn];
int dp[1 << maxm][1 << maxm];
In int DP(int Q, int A)
{
if(~dp[Q][A]) return dp[Q][A];
int cnt = 0;
for(int i = 1; i <= n; ++i) if((Q & a[i]) == A) ++cnt;
if(cnt <= 1) return dp[Q][A] = 0; //符合该特征的物体最多有1个,答案确定,返回
dp[Q][A] = INF;
for(int i = 0; i < m; ++i) if(!((Q >> i) & 1))
dp[Q][A] = min(dp[Q][A], max(DP(Q | (1 << i), A | (1 << i)), DP(Q | (1 << i), A)) + 1);
return dp[Q][A];
}
int main()
{
// MYFILE();
while(scanf("%d%d", &m, &n) && m && n)
{
Mem(a, 0), Mem(dp, -1);
for(int i = 1; i <= n; ++i)
{
scanf("%s", s);
for(int j = 0; j < m; ++j) if(s[j] == '1') a[i] |= (1 << j);
}
write(DP(0, 0)), enter;
}
return 0;
}
``a