题目大意:有n件物品,每件物品有m个特征,可以对特征进行询问,询问的结果是得知某个物体是否含有该特征,要把所有的物品区分出来(n个物品的特征都互不相同)至少需要多少次询问?
考虑状态的表示,可以用一个集合s1表示已经询问过的特征集合,s2表示已经确定物体有的特征集合。在每个状态下,考虑询问哪个特征。首先明确一个问题,在一个状态下,问哪个特征是由你决定,但得到什么回答就不一定了。比如询问一个所有物体中只有一个物体有的特征,如果回答yes那么一击即中。但更坏的情况是回答no,那么还得继续向下找。所以在这个状态下,就要想象对方故意难为你,回答让你询问更多次数的答案。但是选择询问什么是你自己决定的,这就是取min和max的含义。当cnt[s1][s2]=1时是边界,这时无需再询问,因为只有一个物体合法了。
细节比较难处理,cnt[s1][s2]表示询问集合s1得到含有特征集合s2的物体的个数,这个预处理开始没想出来。感觉AC是因为跟着题解的思路走的,很多细节还要再考虑。如果s1,s2状态下没有这样一个物体,也就是状态不合法。这样的话cnt[s1][s2]=0,就可以通过cnt的数值判断出状态合不合法,如果不合法就返回0,取max的时候就会忽略这个状态。然后顺着这个又可以想到,万一有两个转移状态都不合法怎么办?首先,如果走到转移这一步,cnt[s1][s2]肯定>=2,cnt[s1][s2]>=1说明此状态合法。那么询问的这个特征,目标物体要么有要么没有,所以回答有和没有的状态中肯定有一个是合法的
#include<iostream>
#include<cstdio>
#include<cstring>
#define INF (20010)
using namespace std;
int m,n,A[2020],cnt[(1<<11)+5][(1<<11)+5],dp[(1<<11)+5][(1<<11)+5];
char s[2020];
bool vis[(1<<11)+5][(1<<11)+5];
int Dfs(int s1,int s2){
int &ans=dp[s1][s2];
if (vis[s1][s2]) return ans;
vis[s1][s2]=1;
if (cnt[s1][s2]<=1) return ans=0;
ans=INF;
int i;
for (i=1;i<=m;i++)
if (!((1<<(i-1))&s1))
ans=min(ans,max(Dfs(s1|(1<<(i-1)),s2|(1<<(i-1))),Dfs(s1|(1<<(i-1)),s2))+1);
return ans;
}
void Work(){
int i,j,len;
memset(A,0,sizeof(A));
memset(cnt,0,sizeof(cnt));
memset(vis,0,sizeof(vis));
for (i=1;i<=n;i++){
scanf("%s",s);
len=strlen(s);
for (j=0;j<len;j++) if (s[j]-'0') A[i]|=(1<<j);
}
for (i=0;i<=(1<<m)-1;i++)
for (j=1;j<=n;j++)
cnt[i][i&A[j]]++;
printf("%d\n",Dfs(0,0));
}
int main(){
while (cin>>m>>n){
if (!m&&!n) break;
Work();
}
}