题目大意:有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(); } }