CF449D Jzzhu and Numbers (状压DP+容斥)

题目大意:

给出一个长度为n的序列,构造出一个序列使得它们的位与和为0,求方案数

也就是从序列里面选出一个非空子集使这些数按位与起来为0.

看了好久才明白题解在干嘛,我们先要表示出两两组合位与和为0的所有情况

先hx一下每个数出现的次数,然后我们从遍历 i ,i 是二进制的数位

然后遍历所有的情况,如果第 i 位有1,那么说明我们去掉第 i 位的1就是又一种情况!

其实我们统计的是所有数在删掉/不删掉每一位的1 所有可能出现的数!

那么,状态内任意组合,不能取空集,总数就是2^k -1

再根据容斥原理(最玄学的一步),根据状态内1的数量进行容斥,即可得到答案

仍然不是很理解,明天再思考思考

#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
#define N (1<<20)+100
#define maxn 1000000
#define mod 1000000007
using namespace std;

int n;
int hx[N];
ll xx,yy,tt;
ll pw[N],sum[N],ans[22];
void get_pw() {pw[0]=1;for(ll i=1;i<=n+1;i++) pw[i]=(pw[i-1]*(ll)2)%mod;}

int main()
{
    freopen("data.in","r",stdin);
    scanf("%d",&n);
    int x;
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&x);
        hx[x]++;
        sum[x]=hx[x];
    }
    for(int j=0;j<20;j++)
    {
        for(int s=(1<<20)-1;s>=0;s--)
            if(s&(1<<j)) sum[s^(1<<j)]+=sum[s]; 
    }
    get_pw();
    for(int s=0;s<(1<<20);s++)
    {
        int cnt=0;
        for(int j=0;j<20;j++)
            if(s&(1<<j))
                cnt++;
        ans[cnt]+=((pw[sum[s]]-1)%mod+mod)%mod;
        ans[cnt]%=mod;
    }
    ll ret=0;
    for(int i=0;i<20;i++)
    {
        if(i&1) ret-=ans[i],ret%=mod;
        else    ret+=ans[i],ret%=mod;
    }
    printf("%lld\n",(ret%mod+mod)%mod);
    return 0;
}



猜你喜欢

转载自blog.csdn.net/guapisolo/article/details/81812576