CodeForces 449D Jzzhu and Numbers 【DP+容斥】

来源:互联网 发布:北京淘宝纸箱 企业05 编辑:程序博客网 时间:2024/06/13 04:32

题意

给定一个n元集,元素为ai,求其有多少个子集,使得其中的元素ai1,.....aik满足
ai1&ai2&&aik=0
(1n,a106)

分析

要是n和a的范围小一些自然可以直接用01背包做,然而这里a与n都达到了1e6的范围,则要另寻他法。
先求有多少种情况使得与出来的结果不为0,考察有多少元素在某些二进制位上为1,则它们与出来在这些位上也必然为1。 令函数f(x)为满足ai&x=x的a的个数。 这里的x就是一个bitmask,满足ai&x=x的a,也就意味着这些a在x为1的那些二进制位上为1。
那么有了f(x)以后,就可以用容斥原理求出有多少种使得结果不为1的情况。再令g(x)为x的二进制位中1的个数,那么所有结果不为1的情况:

x=1220(1)g(x)+1(2f(x)1)

注意2的幂要减一,因为不能一个也不选。x的上限取到2e20,刚好比1e6大,能满足所有位。
这时再求为0的情况:
2nx=1220(1)g(x)+1(2f(x)1)

=x=1220(1)g(x)(2f(x)1)

现在问题在于如何快速求得f(x)。先将每个a和x分为前k位和后20-k位两部分,A0表示某个a的前k位,A1表示后20-k位,同理有X0X1
设状态:

dp[x][k]A0&X0A1=X1a

转移方程:
dp[x][k]={dp[x][k1]+dp[x+2k][k1]xk0dp[x][k1]xk1

显然dp[x][20]=f(x)

AC代码

//CodeForces 449D Jzzhu and Numbers//AC 2017-1-19 14:21:28//DP, Inclusion-exclusion#include <bits/stdc++.h>using namespace std;const int mod=1e9+7;const int maxn=1e6+100;int n;int a[maxn];int dp[(1<<21)][21];long long powmod(long long x,long long y,long long m){    long long z=x%m;    if(!y)        return 1;    if(y&1)        return z*powmod(z*z,(y-1)/2,m)%m;    else        return powmod(z*z,y/2,m)%m;}int main(){    scanf("%d",&n);    for(int i=0;i<n;++i)        scanf("%d",a+i);    for(int i=0;i<n;++i)        ++dp[a[i]][0];    for(int i=1;i<21;++i)    {        for(int j=0;j<(1<<21);++j)        {            if((j>>(i-1))&1)                dp[j][i]=dp[j][i-1];            else                dp[j][i]=dp[j][i-1]+dp[j+(1<<(i-1))][i-1];        }    }    long long ans=0;    for(int i=0;i<(1<<21);++i)    {        long long cur=powmod(2,dp[i][20],mod)-1;        if(__builtin_popcount(i)&1)            cur=-cur;        ans+=cur+mod;        ans%=mod;    }    cout<<ans<<endl;    return 0;}
0 0