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
上一篇:Js文字特效—文字段逐个变色循环


下一篇:Kaldi的在线自然梯度方法的算法细节