2017 ACM-ICPC 亚洲区(南宁赛区)网络赛:Frequent Subsets Problem

来源:互联网 发布:js判断哪个tr被双击 编辑:程序博客网 时间:2024/05/20 05:29

题目链接:https://nanti.jisuanke.com/t/17320

题目翻译:给出一个数n,和一个浮点数a,数n代表全集U = {1,2,...,n},然后给出

M个U的子集,如果一个集合B(是U的子集),M个集合中有至少M*a个集合包含B,

则B这个集合就是一个满足条件的集合,统计U的子集中B这种集合的个数。


比赛的时候想不到什么好的写法,暴力搜索了一波,超时了(意料之中),最后也每

写出来,今晚特来补题,刚搜索题解,看到大佬们用位运算写的,代码那么短,

真心佩服。


对于N个数的集合,其子集,可以从N个里面挑1个,挑2个,。。。挑N个数构成。

C(n,0)+ C(n,1) + C(n,2)  + C(n,3)  + ..... +  C(n,n) = (1+1)^n = 2^n

N个数子集共有2^n种状态,其中每一个数都代表一个状态,意思就是代表一个子集。

对于输入的M个集合。集合的状态就是2^(num-1)的和。

为何这样?

加入现有一个集合:

1 3 6 10

把它算成 2^(1-1) + 2^(3-1) + 2^(6-1) + 2^(10-1)

这个数字可以用2进制表示:

num(数字):                       10    98 7 6 5      43 2 1

binary bit(二进制位):     1    0      0      0      1      0      0      1      0      1

power(对应的权值):    2^9 2^8  2^7   2^6   2^5  2^4  2^3   2^2   2^1   2^0

对于每个状态,是一个数,它也是一个二进制串,二进制串中为1的位置,代表了

某个数字存在与集合M,当每个数与集合M进行与运算的时候,可以求出同为1的

二进制位,得出的数与当前这个相同,则说明当前数代表的状态是集合M的子集。


AC代码:

#include <iostream>#include <stdio.h>#include <math.h>#include <string.h>using namespace std;char str[1000];int M[55];int main(){    int n,m;    double a;    scanf("%d %lf ",&n,&a);    memset(M,0,sizeof(M));    m = 0;    while(gets(str))    {        int num = 0;        int len = strlen(str);        for(int i = 0; i < len; i++)        {            if(str[i]==' ')            {                M[m] = M[m] + (1<<(num-1));                num = 0;            }            else            {                num = num*10 + str[i]-'0';            }        }        M[m++] += (1<<(num-1));    }    int temp = ceil(m*a);    int state = (1<<n);    int ans = 0;    for(int i = 1; i <= state; i++)    {        int sum = 0;        for(int j = 0; j < m; j++)        {            /**此处i&M[j]必须用括号括住,刚开始我没有扩,一直输出答案就是0,            急死我了,一直觉得代码没错,但是答案就是输出0.坑爹。**/            if((i&M[j]) == i)                sum++;        }        if(sum >= temp)            ans++;    }    printf("%d\n",ans);    return 0;}


阅读全文
2 0