Codeforces Round #257 (Div. 1) D. Jzzhu and Numbers

D. Jzzhu and Numbers
time limit per test2 seconds
memory limit per test256 megabytes
inputstandard input
outputstandard output
Jzzhu have n non-negative integers a1, a2, …, an. We will call a sequence of indexes i1, i2, …, ik (1 ≤ i1 < i2 < … < ik ≤ n) a group of size k.

Jzzhu wonders, how many groups exists such that ai1 & ai2 & … & aik = 0 (1 ≤ k ≤ n)? Help him and print this number modulo 1000000007 (109 + 7). Operation x & y denotes bitwise AND operation of two numbers.

Input
The first line contains a single integer n (1 ≤ n ≤ 106). The second line contains n integers a1, a2, …, an (0 ≤ ai ≤ 106).

Output
Output a single integer representing the number of required groups modulo 1000000007 (109 + 7).

Examples
inputCopy
3
2 3 3
outputCopy
0
inputCopy
4
0 1 2 3
outputCopy
10
inputCopy
6
5 2 0 5 2 1
outputCopy
53

题意:给出n个数,问有多少个子序列与起来的和为0,
做法:可以想到是容斥,答案就是2^n-1-sum:(-1)^(x+1)*f[x];1<=x<=19;x为子序列与起来至少有x个位置为1的数量。
有一个想法是把每一个数分解,分解成只有一个位置为1,有两个位置为1,然后把这样的数的数量存到a数组里,那么对于f[x],就是a[y],所有y的1的个数为x个,sum:2^a[y]-1.但是这样的复杂度是n^2;
然后发现对于一个数,可以通过枚举他所有为1的位数,这一位是否为1,来查找所有的情况,
假设从第0为开始枚举,枚举到第19位,当枚举到第i为的时候,可以用(p,q)来表示某一个数z,前i-1位枚举的状态,q代表还没有枚举的位的情况(已经枚举了的为对之后的枚举不会产生影响),那么(p,q)其实就是(p,z>>(i-1)),p是z在第0位到第i-1位上为1的位置,为0或未1的所有情况,那么可以发现对于枚举第i为的时候,(p,q)的方案数最多有2^(i-1)*2^(19-i)种,所以最多只有2^20种状态,那么用这些状态去转移,就好了,状态转移就是如果q&1,那么就把这一位为0,为1两种情况加到下一个状态,不然只把为0的情况加到下面。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
int num[N];
int qp[N];
int dp[25][(1<<20)+7];
const int mod = 1e9+7;
int get(int x){
    int cnt = 0;
    while(x){
        if(x&1) cnt++;
        x >>= 1;    
    }
    if(cnt%2 ==0) return -1;
    return 1;
}

void add(int &x,int y){
    x += y;
    if(x >= mod) x -= mod;
    if(x < 0) x += mod;

}

int main(){
    qp[0] = 1;
    for(int i = 1;i < N;i ++) qp[i] = qp[i-1]*2LL%mod;
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++){
        scanf("%d",&num[i]);    
    }
    for(int i = 1;i <= n;i ++){
        add(dp[0][num[i]],1);
        if(num[i]&1){
            add(dp[0][num[i]^1],1);
        }
    }
    for(int i = 1;i <= 19;i ++){
        for(int j = 0;j< 1<<20;j ++){
            add(dp[i][j],dp [i-1][j]);
            if(j&(1<<i)) add(dp[i][j^(1<<i)],dp[i-1][j]);
        }
    }
    int ret = 0;
    for(int i = 1;i <(1<<20);i ++){
        add(ret,get(i)*(qp[dp[19][i]]));
    }
    //ret = (qp[n]-ret+mod)%mod;
    int ans= qp[n];
    add(ans,-ret);//这里关于一个都不取这个状态,在上面也被容斥了,所以直接减就好了
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值