[每日一题] 3.29 Twenty Questions UVa1252 [状压dp]

Twenty Questions UVa1252

解题思路

为了叙述方便,设“心里想的物体”为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(n
2m),所以总时间复杂度为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;
}

感觉这道题对我这个弱鸡,还是非常难的

猜你喜欢

转载自blog.csdn.net/AdamAndTina/article/details/88897348
今日推荐