UVa1252二十个问题

题目描述:

有n个物体和m个特征,每个物体用一个01串表示,第i位为1说明该特征该物体具有。我在心里想一个这n个物体的其中一个,你每次询问一个特征,我回答这个物体是否具备这个特征,当你确定答案后把答案告诉我。假设你采取最优策略,最少询问多少次就能保证猜到?(m<=11,n<=128)

思路:集合上的DP

因为每个特征不用问两遍,所以题目意思大致是问至少问多少个特征可以把所有物品区分开。决策就是问哪个特征,状态就是反馈回来的信息(在不在目标物体里)。

定义状态:d[s][a]:在所有问过的特征集合s中,集合a是目标里有的。很显然a是s的子集。

如何转移?假设现在要做决策,问 k 特征。那么有 两种情况:
1 目标里没有 k 这个特征
2 目标里有 k 这个特征

这个 k 其实把物品分成两堆,递归,取两堆当中需要问的次数最多的(保证能猜到)。这个结果不见得最优,所以需要取最小值。

容易想到,要问那些易于把物体区分开的特征,也就是如果所有物体都有k特征或都没有,这时候问k是没有意义的。所以,上面分成两堆的时候可以判断一下,如果任意一堆为空,这个决策都是无意义的,直接进行下一个。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int INF = 0x3f3f3f3f;
const int maxn = 130;
const int maxm = 11;

int d[1<<maxm][1<<maxm], w[maxn];
int n, m;

// s: 已经问过的特征集合
// a: s中目标物体里包含的特征集合 
int dp(int s, int a){
	int& ans = d[s][a];
	if(ans != INF) return ans;
	int c = 0; // 统计满足条件的个数。条件:物体的特征集合 与 s 的交集 == a; 
	for(int i = 0; i < n; ++i) if((w[i] & s) == a) ++c;
	// 1个物体就不需要问了,2个物体最少需要问一次。 
	if(c <= 1) return ans = 0;
	if(c == 2) return ans = 1;
	
	for(int f = 0; f < m; ++f){
		if(s&(1<<f)) continue; // 如果 f 特征问过了 
		ans = min(ans, max(dp(s|(1<<f),a|(1<<f)), dp(s|(1<<f),a)) + 1);
	}
	return ans;
}
int main()
{
	//freopen("in.txt","r",stdin);
	while(scanf("%d %d",&m,&n) == 2&&n){
		memset(d,INF,sizeof(d));
		memset(w,0,sizeof(w));
		
		char str[maxn];
		for(int i = 0; i < n; ++i){
			scanf("%s",str);
			for(int j = 0; str[j]; ++j) if(str[j] == '1') w[i] |= (1<<j);
		}
		
		printf("%d\n",dp(0,0));
	}

	return 0;
}

优化。加上判断:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 128;
const int maxm = 11;

int n,m, kase;
char objects[maxn][maxm];
int d[1<<maxm][1<<maxm], vis[1<<maxm][1<<maxm];
int cnt[1<<maxm][1<<maxm];

void init(){
	for(int s = 0; s < (1<<m); ++s){
		for(int a = s; a ; a = (a-1)&s)
			cnt[s][a] = 0;
		cnt[s][0] = 0;
	}
	for(int i = 0; i < n; ++i){
		int features = 0;
		for(int f = 0; f < m; ++f)
			if(objects[i][f] == '1') features |= (1<<f);
		for(int s = 0; s < (1<<m); ++s)
			++cnt[s][s & features];
	}
}

int solve(int s, int a){
	if(cnt[s][a] <= 1) return 0;
	if(cnt[s][a] == 2) return 1;
	
	int& ans = d[s][a];
	if(vis[s][a] == kase) return ans; // 省去了初始化vis数组,节省时间 
	vis[s][a] = kase;
	
	ans = m;
	for(int f = 0; f < m; ++f){
		if(!(s & (1<<f))){  // f 特征还没有猜过 
			int s2 = s|(1<<f);
			int a2 = a|(1<<f);
			if(cnt[s2][a2] >= 1 && cnt[s2][a] >= 1){
				int need = max(solve(s2, a2), solve(s2, a)) + 1;
				ans = min(ans, need);
			}
		}
	}
	return ans;
}

int main()
{
	freopen("in.txt","r",stdin);
	kase = 0;
	while(scanf("%d %d",&m,&n) == 2&&n){
		++kase;
		for(int i = 0; i < n; ++i) scanf("%s",objects[i]);
		init();
		printf("%d\n",solve(0,0));
	}

	return 0;
}


猜你喜欢

转载自blog.csdn.net/CY05627/article/details/88663902