题目
(题目感觉好萌呀)
思路
(本题较难)
首先,设“心里想的物体”为W,集合s为已经询问过的特征,集合a为s中确定W具备的特征,其中一定有
。设d(s,a)为已经询问了特征集s,且确定W具备的为a时,还需要询问的最小次数。这里考虑如何决策:
决策为下一次提问的特征k,k
[0,m)且k不在s中,则下一次提问k时的询问次数是:
。状态转移时需要枚举所有的k,然后找出询问次数的最小值即可。
此处为了避免在dp的时候判断,要先预处理出cnt[s][a],即在s和a的情况下,符合的物体的数量。
显然此处的预处理需要枚举,最容易想到的枚举是,遍历s,a(a作为s的子集),然后再遍历n寻找符合s和a的物体。这个枚举的时间复杂度在图的色数里面谈过,
,比较大,可以采用技巧枚举。
遍历s和n,直接根据物体来填cnt数组:
_for(s, 0, (1 << m))
_for(i, 0, n)
cnt[s][s & st[i]]++;
这样时间复杂度就控制在了
。
1.状态定义:d(s,a),已经询问了特征集s,且确定W具备的特征为a集时,还需要询问的最小次数。
2.边界:当一个物体“具备集合a中的所有特征,不具备集合s-a中的所有特征”时,d(s,a) = 0
3.答案:d(0,0)
4.状态转移方程:
5.时间复杂度:
(在预处理时还有 的复杂度,但在本题规模中可以忽略不计)
再次强调位运算的优先级问题,下面代码可以这么写
if ((s & (1 << k)) == 0)
但不能这么写
if (s & (1 << k) == 0)
位运算的优先级甚至比==还低。
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int INF = 1000;
const int maxn = 128 + 2;
const int maxm = 11 + 1;
int n, m, st[maxn], cnt[1 << maxm][1 << maxm], d[1 << maxm][1 << maxm];
int dp(int s, int a) {
if (cnt[s][a] == 1) return 0;
if (cnt[s][a] == 0) return INF;
int &ans = d[s][a];
if (ans != -1) return ans;
ans = INF;
_for(k, 0, m)
if ((s & (1 << k)) == 0) { // &的优先级甚至比==还小,不能写成 if (s & (1 << k) == 0)
ans = min(ans, max(dp(s | (1 << k), a | (1 << k)), dp(s | (1 << k), a))+1);
}
return ans;
}
int main() {
while (scanf("%d%d", &m, &n) == 2 && m && n) {
char s[100];
int code;
_for(i, 0, n) {
scanf("%s", s);
code = 0;
_for(j, 0, m)
if (s[m-j-1] == '1')
code |= (1 << j);
st[i] = code;
}
memset(cnt, 0, sizeof(cnt));
_for(s, 0, (1 << m))
_for(i, 0, n)
cnt[s][s & st[i]]++; // a表示已经询问的s中具备的
memset(d, -1, sizeof(d));
printf("%d\n", dp(0, 0));
}
return 0;
}