题目大意:
一个表示数字的LED管,即使有些坏掉了,也能把所要表达的数字唯一表示出来。问题拓展开来,给出一些由0或1组成的序列,长度是p,问最少用多少个长度可以把这些序列唯一的表示出来。
问题分析:
题目中的数据范围都很小,粗略估计一些,三层for也可以过。直接用二进制枚举它们的子集就可以。然后判断是否符合要求,结果输出最小的那个值。
做这道题目之前看了刘汝佳白书里面的子集生成,里面推荐了3种做法。分别是增量构造法,位向量法和二进制法。前两种方法都是用递归构造的,而二进制法则是直接枚举,本题使用的就是第三种方法。
1:增量构造法,思路是一次选出一个元素放到集合中,用递归的方法构造子集,觉得相对后面两种方法稍难理解些。
2:位向量法,构造一个位向量B[i],而不是直接构造子集本身,B[i]=1,表示i在构造的子集中,等于0,表示不在。学习的时候感觉相比于第一种方法简单许多,而且书上画的解答树的图也帮助理解了许多,把以前对递归不太懂的地方更好理解了,收获很大。
void print_subset(int n,int *B,int cur)
{
if(cur==n)
{
for(int i=0;i<n;i++)
if(B[i])
cout<<i<<" ";
cout<<endl;
return ;
}
B[cur]=1;
print_subset(n,B,cur+1);
B[cur]=0;
print_subset(n,B,cur+1);
}
3,二进制法,一个二进制数当中,从右往左的第i位表示元素i是否在集合中。如二进制0100011000110111代表集合{0,1,2,4,5,9,10,14}。
连个集合A&B,A|B,A^B分别代表集合的交,并和对称差。0代表空集。全集往往声明为(1<<n)-1 ,则A的补集为((1<<n)-1)^A。
代码更加的简单,也很好理解。
<pre name="code" class="cpp"> int n=5;
for(int i=0;i < (1<<n);i++)
{
for(int j=0;j<n;j++)
if(i&(1<<j))
cout<<j<<" ";
cout<<endl;
}
AC代码:
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<string>
#include<cstring>
#include<math.h>
#include<algorithm>
#include<assert.h>
#include<stdlib.h>
#include<stack>
#include<vector>
#include<map>
#include<set>
#define pi acos(-1.0)
typedef long long LL;
using namespace std;
int main()
{
int t;
cin>>t;
int led[110][20];
int tmp[110][20];
int flag[70000];
while(t--)
{
int ok,ans;
int res=999999;
int p,n,tt;
cin>>p>>n;
for(int i=0; i<n; i++)
for(int j=0; j<p; j++)
scanf("%d",&led[i][j]);
for(int i=0; i<(1<<p); i++)
{
for(int s=0; s<n; s++)
{
tt=0;
for(int j=0; j<p; j++)
{
if(i&(1<<j))
tmp[s][tt++]=led[s][p-j-1];
}
}
memset(flag,0,sizeof(flag));
ok=1;
for(int k=0; k<n; k++)
{
int sum=0;
for(int j=0; j<tt;j++)
{
if(tmp[k][j])
sum+=1<<j;
}
if(flag[sum])
{
ok=0;
break;
}
else
flag[sum]=1;
}
if(ok)
{
ans=0;
for(int j=0;j<p;j++)
{
if(i&(1<<j)) //&和 &&千万不要写错。。。
ans++;
}
res=min(ans,res);
}
}
printf("%d\n",res);
}
return 0;
}