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; }
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)$