bzoj 2839 集合计数

n 个元素,选出若干子集使得交集大小为 k,求不同选择方案数

$n,k \leq 10^6$

sol:

首先,先选 k 个元素为交集,这一步是 $C_{n}^{k}$

剩下就是选择一些没有交集的集合方案数

考虑容斥,交集为 $\emptyset$ 的方案数 = 随便选的方案数 - 交集大于等于 1 的方案数 + 交集大于等于 2 的方案数 ...

然后考虑求交集大于等于 i 的方案数

跟上面一样,先选出来 i 个,剩下的元素的集合任意选,选出来是 $C_{n}^i \times (2 ^ {2 ^ {(n - i)}} - 1)$ 种,然后原式就是

$\sum_{i=0}^n (-1)^i \times C_{n}^i \times (2 ^ {2 ^ {(n - i)}} - 1)$

求一下就可以了

听说叫做二项式反演

#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline int read()
{
    int x = 0,f = 1;char ch = getchar();
    for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f;
    for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0';
    return x * f;
}
const int mod = 1000000007,maxn = 1000000 + 50;
int n,k;
int fac[maxn],ifac[maxn];
inline int ksm(int x,int t)
{
    int res = 1;
    while(t)
    {
        if(t & 1)res = 1LL * res * x % mod;
        x = 1LL * x * x % mod;
        t = t >> 1;
    }return res;
}
inline int C(int n,int m)
{
    if(n < 0 || m < 0 || n < m)return 0;
    return (((1LL * ifac[m] * ifac[n - m]) % mod) * fac[n]) % mod;
}
int main()
{
    n = read(),k = read();
    fac[0] = 1;for(int i=1;i<=n;i++)fac[i] = (1LL * fac[i - 1] * i) % mod;
    ifac[n] = ksm(fac[n],mod - 2);for(int i=n-1;~i;i--)ifac[i] = (1LL * ifac[i + 1] * (i + 1)) % mod;
    LL ans = 0,now = 2;n -= k;
    for(int i=n;~i;i--)
    {
        (ans += ((i & 1) ? -1 : 1) * C(n,i) * (now - 1) % mod) %= mod;
        (now *= now) %= mod;
    }n += k;(ans *= C(n,k)) %= mod;
    ans = (ans + mod) % mod;
    cout<<ans<<endl;
}
View Code

 

upd:formal 的学了一下二项式反演

如果有 $f(n) = \sum\limits_{i=1}^nC_n^i g(i)$ ,则有 $g(n) = \sum\limits_{i=1}^n (-1)^{(n-i)} \times C_n^i f(i)$

posted @ 2019-01-07 09:19  探险家Mr.H  阅读(146)  评论(0编辑  收藏  举报