总结「二项式反演」
转载注明来源:https://www.cnblogs.com/syc233/p/13455373.html
二项式反演 ~ Inversion of the Binomial
前置知识 容斥原理
众所周知:
二项式反演
记 \(\complement A\) 为 \(A\) 的补集。设全集为 \(S\) ,则有:
我们可以对容斥的式子进行变形:
将容斥的式子中 \(A_i\) 替换为 \(\complement A_i\) ,再进行相同的变形:
考虑一种特殊情况:对于任意一个集族 \(\mathcal{U}=\{A_1,A_2,A_3,\cdots,A_n\}\) ,其中任意 \(i\) 个集合的交集都为 \(g(i)\) ,则:
并且定义 \(g(0)=|S|\) 。
类似地,令
将 \(f,g\) 代入 \((1),(2)\) 两式中得:
二项式反演的不同形式
形式一
就是上面那个式子。
形式二
这个形式比较好推。不难发现其中 \(g(i)\) 即为形式一中的 \((-1)^ig(i)\) ,代入即可。
形式三
将右式代入左式可得证。
BZOJ2839 集合计数
题面
一个有 \(N\) 个元素的集合有 \(2^N\) 个不同的子集。选出若干个子集(至少一个),使得其交集的元素个数恰好为 \(K\),求有多少种取法。
题解
设 \(f(k)\) 表示选定 \(k\) 个交集元素的方案数,则有:
式中 \({n \choose k}\) 表示选定 \(k\) 个元素的方案数。选定 \(k\) 个元素后,还剩下 \(n-k\) 个元素,即有 \(2^{n-k}\) 个集合可以选择,即有 \(2^{2^{n-k}}-1\) 种选择集合的方案(至少选一个集合,除去空集)。
设 \(g(i)\) 表示交集元素恰好为 \(i\) 个的方案数。因为选定 \(k\) 个交集元素后,实际的交集元素个数只会大于等于 \(k\) ,则有:
这就是一个典型的二项式反演的形式三,可以得到:
于是就可以 \(O(n)\) 求 \(g(k)\) 了。
\(\text{Code:}\)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 1000005
#define R register
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;
const lxl mod=1e9+7;
inline lxl read()
{
lxl x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
lxl N,K;
lxl inv[maxn],invf[maxn],fac[maxn];
inline void init()// 预处理一下逆元
{
inv[1]=invf[1]=invf[0]=fac[1]=fac[0]=1;
for(lxl i=2;i<=N;++i)
{
inv[i]=(-(mod/i)*inv[mod%i]%mod+mod)%mod;
invf[i]=invf[i-1]*inv[i]%mod;
fac[i]=fac[i-1]*i%mod;
}
}
inline lxl C(int n,int m)
{
return fac[n]*invf[n-m]%mod*invf[m]%mod;
}
int main()
{
// freopen("P2839.in","r",stdin);
N=read(),K=read();
init();
lxl ans=0,tmp=1,type=((N-K)&1)?-1:1;
for(int i=N;i>=K;--i)
{
ans=(ans+type*C(i,K)%mod*C(N,i)%mod*tmp%mod+mod)%mod;
tmp=tmp*(tmp+2)%mod;
type=-type;
}
printf("%lld\n",ans);
return 0;
}
LUOGU P4859 已经没有什么好害怕的了
按住老虚
题面
给定两个长为 \(N\) 的数列 \(A,B\) ,将 \(A\) 与 \(B\) 中的数两两配对,求满足 \(A_i>B_j\) 的数对数比 \(A_i<B_j\) 的数对数恰好多 \(k\) 的配对方案数。
题解
设 \(A_i>B_j\) 的数对数为 \(m\) ,则 \(A_i<B_j\) 的数对数为 \(n-m\) ,由 \(m-(n-m)=k\) 解出 \(A_i>B_j\) 的数对数 \(m=\frac{n+k}{2}\)。
首先 \(n+k \equiv 1 \ ({\rm{mod}} \ 2)\) 时无解,特判掉这种情况。
将 \(A,B\) 排序。设 \(dp_{i,j}\) 表示考虑了数列 \(A\) 中的前 \(i\) 个数,组成了至少 \(j\) 个满足 \(A_i>B_j\) 的数对的方案数(这里并没有考虑其他数的不同组合方案)。则转移:
-
一种情况是 \(A_i\) 对答案不做出贡献,那么 \(dp_{i,j}\) 直接从 \(dp_{i-1,j}\) 转移过来。
-
另一种情况是 \(A_i\) 与 \(B\) 中一个数对答案产生 \(1\) 的贡献。设 \(B\) 中比 \(A_i\) 小的数有 \(cnt\) 个,其中有 \(j-1\) 个数与 \(A_i\) 之前的数配对,则 \(A_i\) 有 \(cnt-(j-1)\) 种配对方案,那么 \(dp_{i,j}\) 从 \(dp_{i-1,j-1} \times (cnt-j+1)\) 转移而来
综上有:
设 \(f(m)\) 表示至少有 \(m\) 个 \(A_i>B_j\) 的数对的方案数,\(g(m)\) 表示恰好有 \(m\) 个 \(A_i>B_j\) 的数对的方案数。则有:
因为除了指定的 \(m\) 个满足 \(A_i>B_j\) 的数对,\(A\) 中还有 \(n-m\) 个数可以与 \(B\) 中 \(n-m\) 个数自由组合,则有:
由二项式反演的形式三可得:
直接求这个和式即可。
\(\text{Code}:\)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 2005
#define R register
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;
const lxl mod=1e9+9;
inline lxl read()
{
lxl x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
int n,k,m;
lxl a[maxn],b[maxn],f[maxn][maxn];
lxl inv[maxn],invf[maxn],fac[maxn];
inline void init()
{
inv[1]=inv[0]=invf[0]=invf[1]=fac[0]=fac[1]=1;
for(lxl i=2;i<=n;++i)
{
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
invf[i]=invf[i-1]*inv[i]%mod;
fac[i]=fac[i-1]*i%mod;
}
}
inline lxl C(int n,int m)
{
return fac[n]*invf[n-m]%mod*invf[m]%mod;
}
int main()
{
// freopen("P4859.in","r",stdin);
n=read(),k=read();
if((n+k)&1) {puts("0");return 0;}
init();
m=(n+k)>>1;
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=1;i<=n;++i)
b[i]=read();
sort(a+1,a+n+1);
sort(b+1,b+n+1);
f[0][0]=1;
for(int i=1;i<=n;++i)
{
int cnt=lower_bound(b+1,b+n+1,a[i])-b-1;
f[i][0]=1;
for(int j=1;j<=n;++j)
f[i][j]=(f[i-1][j]+f[i-1][j-1]*(cnt-j+1)%mod)%mod;
}
lxl ans=0;
for(int i=m,type=1;i<=n;++i,type=-type)
ans=(ans+type*C(i,m)%mod*fac[n-i]%mod*f[n][i]%mod+mod)%mod;
printf("%lld\n",ans);
return 0;
}