最近两天状态不佳……所以刷的题目并不多。让人更悲伤的其实还是,大部分是水题。。
但是刷完这道题的时候意识到不能这么水下去了~所以决定深入研究一下子集生成,当然啦,是基于刘汝佳老师的算法入门经典,这里只是将其加以应用,仅此而已。
一、首先是我个人认为最好理解的子集生成方法——位向量法:
#include <cstdio>
#include <cstring>
using namespace std;
int a[110][25];
char s[110][25];
int flag[25];
int min_;
int p,n;
int subset(int n,int p,int cur)
{
if(cur==p)
{
int ct=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<p;j++)
{
if(flag[j])
{
ct++;
s[i][j]=a[i][j]+'0';
}
else
s[i][j]=0+'0';
}
}
for(int i=0;i<n;i++)
{
s[i][p]=0;
}
for(int i=0;i<n-1;i++)
{
for(int j=i+1;j<n;j++)
{
if(!strcmp(s[i],s[j]))
return 0;
}
}
if(min_>ct/n)
min_=ct/n;
return 0;
}
flag[cur]=0;
subset(n,p,cur+1);
flag[cur]=1;
subset(n,p,cur+1);
return 1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(flag, 0, sizeof(flag));
scanf("%d%d",&p,&n);
for(int i=0;i<n;i++)
{
for(int j=0;j<p;j++)
scanf("%d",&a[i][j]);
}
min_=p;
subset(n,p,0);
printf("%d\n",min_);
}
return 0;
}
二、是刚开始不怎么明白的——增量构造法:(菜鸟看了好久算搞清楚情况,弱爆了。。)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
const int maxn=110;
int B[maxn][20],min,A[20],n,p;
int is_ok(int cur)//这里是根据题目要求来的。
{
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
{
int ok=1;
for(int k=0;k<cur;k++)
if(B[i][A[k]]!=B[j][A[k]]) {ok=0;break;}
if(ok) return 0;
}
return 1;
}
void try_subset(int cur)//这个函数就是用来实现增量构造法的。判断合不合适用的是上面的函数is_ok()。
{
if(min>cur &&is_ok(cur)) min=cur;
int s=cur?A[cur-1]+1:0;
for(int i=s;i<p;i++)
{
A[cur]=i;
try_subset(cur+1);
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&p,&n);
int i,j;
for(i=0;i<n;i++)
for(j=0;j<p;j++)
scanf("%d",&B[i][j]);
min=15;
try_subset(0);
printf("%d\n",min);
}
return 0;
}
是的,我看明白了~看不明白怎么办?那就多看几遍~嗯!就是这个道理
三、是高大上威武雄壮的——二进制法:
介个……理解是不难,但真的要自己运用起来恐怕不会那么容易。
这个自己不会用,要多解释一番:
#include <cstdio>
#include <cstring>
const int P = 1 << 15, maxn = 105;
bool vis[P + 5];
int a[maxn];
int count(int n)//统计n的二进制中1的个数
{
int sum = 0;
while (n)
{
if (n & 1)//和1做与运算,不停右移直到为零。如果最右位为一,则sum会加1;
++sum;
n >>= 1;
}
return sum;
}
int main()
{
int t, p, n, x, i, j, ans, up;
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &p, &n);
for (i = 0; i < n; i++)
{
scanf("%d", &a[i]);
for (j = 1; j < p; j++)
{
scanf("%d", &x);
a[i] = (a[i] << 1) + x;//把所给出的0和1的串压缩成一个数字,此处的p不能太大,否则可能会溢出。
//其实这个地方是和上面统计1的个数相对应的,只不过用右移左移运算对电脑来说更容易,更能节省时间。
}
}
ans = p, up = 1 << p;
for (i = 0; i < up; i++)//枚举,从0开始枚举到2的p次方减1.你知道一个有n个元素的集合有2的n次方个子集的;
{
int bits = count(i);
if (bits >= ans) continue;//比当前最小结果大的时候就不用继续判断了,不符合。
memset(vis, 0, sizeof(vis));
for (j = 0; j < n; ++j)
{
int pos = a[j] & i;//a[j]在i下的显示结果(这能确定两个数相同的位的个数和相同位的位置——形成的数是唯一的)
if (vis[pos])//说明有两个数显示是一样的
break;
vis[pos] = true;
}
if (j < n) continue;
ans = bits;
}
printf("%d\n", ans);
}
return 0;
}