解题思路
为了叙述方便,设“心里想的物体”为W。首先在读入时把每个物体转化为一个二进制整数。不难发现,同一个特征不需要问两遍,所以可以用一个集合s表示已经询问的特征集。
在这个集合s中,有些特征是W所具备的,剩下的特征是W不具备的。用集合a来表示“已确认物体W具备的特征集”,则a一定是s的子集。
设d(s,a)表示已经问了特征集s,其中已确认W所具备的特征集为a时,还需要询问的最小次数。如果下一次提问的对象是特征k(这就是“决策”),则询问次数为:max{d(s+{k},a+{k}),d(s+{k}, a)}+1考虑所有的k,取最小值即可。边界条件为:如果只有一个物体满足“具备集合a中的所有特征,但不具备集合s-a中的所有特征”这一条件,则d(s,a)=0,因为无须进一步询问,已经可以得到答案。
因为a为s的子集,所以状态总数为3m,时间复杂度为O(m3m)。对于每个s和a,可以先
把满足该条件的物体个数统计出来,保存在cnt[s][a],避免状态转移的时候重复计算。统计cnt[s][a]的方法是枚举s和物体,时间复杂度为O(n2m),所以总时间复杂度为O(n2m +m3m)。对于本题的规模来说O(n*2m)可以忽略不计。
——《算法竞赛入门经典 第二版》
我的代码
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXM = 128; // m是人
const int MAXN = 11; // N是特征个数
const int INIFITE = 1000;
int m, n;
int d[1<<MAXN][1<<MAXN], p[MAXM], cnt[1<<MAXN][1<<MAXN];
bool vis[MAXN];
char str[MAXN];
bool read () {
scanf("%d %d", &n, &m);
if (m == 0) return false;
for (int i = 0; i < m; i++) {
int b = 0;
scanf("%s", str);
for (int j = 0; j < n; j++) {
b = b | ((str[j] - '0')<<j);
}
p[i] = b;
}
memset(vis, 0, sizeof(vis));
memset(d, -1, sizeof(d));
memset(cnt, -1, sizeof(cnt));
return true;
}
bool judge (int s, int s0) {
int c = 0;
if (cnt[s][s0] == -1) {
for (int j = 0; j < m; j++) {
if (((p[j]&s)^s0) == 0) c++;
if (c > 1) break;
}
cnt[s][s0] = c;
}
return cnt[s][s0] == 1;
}
// s是已经询问过的特征的集合,s0是询问过的且具备的特征的集合
int dp (int s, int s0) {
if (d[s][s0] != -1) return d[s][s0];
d[s][s0] = INIFITE;
if (judge(s, s0)) {
d[s][s0] = 0;
return 0;
}
for (int i = 0; i < n; i++) {
if (!vis[i]) {
vis[i] = true;
int b = (1<<i);
d[s][s0] = min(d[s][s0], max(dp(s|b, s0|b), dp(s|b, s0))+1);
vis[i] = false;
}
}
return d[s][s0];
}
int main () {
while (read()) {
if (m == 1) printf("0\n");
else {
printf("%d\n", dp(0, 0));
}
}
return 0;
}
感觉这道题对我这个弱鸡,还是非常难的