CF 449D 题解(状压dp+容斥原理)

题目大意:给定一个序列,选择其中的一部分数,他们相互&的和为0。

f[i]数组表示有多少个数相互&结果都是i,就是这个数i是多少个数的子集

可以先枚举每位,再枚举每个数字,如果这个数字当前位为1,那么当前位的值可以作为

删去这个位的值。这样按位数枚举,每个位都由前一个位推过,不会重复,而且枚举

位数和每个数保证了完全性。

求出f之后,dp[i]表示大于等于i个1的数的个数,枚举每个数(注意这里可以为0,

含义为答案可能为的数),再枚举每个位置,算出这个数的为1的个数tot,

那么dp[tot]加上2^(f[i])-1,也就是所有可以&出这个位数的数每个数选或不选都可以

那么就是2的幂次个,去掉都不选的情况,最后利用容斥原理,

ans = dp[0]-dp[1]+dp[2]……

因为每个数都是互相不重复的,也保证了算法的正确性。

下面代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define mode 1000000007
#define N 1000006
using namespace std;
typedef long long ll;
ll f[N];
ll ho[N];
ll dp[25];
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1;i <= n;i++)
    {
        int x;
        scanf("%d", &x);
        f[x]++;
    }
    ho[0] = 1;
    for(int i = 1;i <= N;i++)
    {
        ho[i] = ho[i-1]*2%mode;
    }
    for(int i = 0;i <= 20;i++)
    {
        for(int j = 1;j <= N;j++)
        {
            if((1<<i)&j)
            {
                f[j^(1<<i)] += f[j];
                f[j] %= mode;
            }
        }
    }
    for(int i = 0;i <= N;i++)
    {
        ll tot = 0;
        for(int j = 0;j <= 20;j++)
        {
            if((1<<j)&i)
            {
                tot++;
            }
        }
        dp[tot] += ((ho[f[i]]-1)%mode+mode)%mode;
        dp[tot] %= mode;
    }
    ll ans = 0;
    for(int i = 0;i <= 20;i++)
    {
        if(i%2)
        {
            ans -= dp[i];
            ans %= mode;
        }else
        {
            ans += dp[i];
            ans %= mode;
        }
    }
    printf("%I64d",(ans%mode+mode)%mode);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zzk_233/article/details/81782259