题意如下:
给出M个特征和N个物体。每个物体有M个特征中的某一些。现在给出N个物体中的一个物体,可以询问是否具有某项特征,问至少需要多少次询问,才能确认这个物体。
题目链接:https://vjudge.net/problem/UVA-1252
题目分析:因为特征数最多只有11个,很容易的想到状压DP。而且这个问题是一个取与不取得问题,那状态肯定有两种状压方式。对于当前阶段,就有取和不取两个衍生点。我们令DP[S][A]表示在考虑了S集合的特征之后,具有了A集合特征的01字符串(物体)个数。详见代码。
代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
using namespace std;
const int maxx = (1<<11)+500;
int n,m;
int dp[maxx][maxx];
int cnt[maxx][maxx];//cnt用来预处理,cnt[s][a]表示已考虑特征集合为S,当前具有的特征集合为A时候的有几个满足的01字符串。
char str[20];
void init(){
memset(dp,-1,sizeof(dp));
memset(cnt,0,sizeof(cnt));
}
void insertStr(char s[]){//题目给出的事01串,我们需要把他看成二进制数字然后转化为十进制。
int len = strlen(s);
int aa = 0;
for(int i=len-1;i>=0;i--){
if(s[i]=='1')aa|=(1<<(len-i-1));
}
for(int i=0;i<(1<<n);i++){//对于每一个字符串,找到所有S和他的共同特征集合并计数。
cnt[i][i&aa]++;
}
}
int dfs(int s,int a)
{
if(cnt[s][a]==1)return 0;//如果当前考虑集合为S,只有一个01字符串满足A,那么就唯一识别了一个字符串,还需要0次询问
if(cnt[s][a]==2)return 1;//如果还有两个字符串需要区分,那么只需要一次询问就能区分。
int &ans = dp[s][a];
if(ans!=-1)return ans;
ans = n;
for(int k=0;k<n;k++){
int aa = (1<<k);
int res = 0;
if((s&aa)==0){
int s2 = s|aa;//当前总的集合增加
int a2 = a|aa;//选取该询问
if(cnt[s2][a]>=1&&cnt[s2][a2]>=1){
res = max(dfs(s2,a),dfs(s2,a2))+1;//为什么要取最大值呢?
//因为题目意思是要我们找到最小的询问次数,使得无论我们找哪一个字符串,我们都能找到(which every object in the set is identifiable)。
//所以我们必须考虑最难找的情况。
ans = min(ans,res);//但是我们可以通过调整询问问题的次序的不同来找到最小的res(最大次数)。
}
}
}
return ans;
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)break;
init();
for(int i=0;i<m;i++){
scanf("%s",str);
insertStr(str);
}
int res = dfs(0,0);
printf("%d\n",res);
}
return 0;
}
PS:状态不是很好想,不过以后记住这种操作就可以了。