題目大意:
一個表示數字的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;
}