二项式反演学习笔记
形式
考虑 \(m\) 个有标号的物品。
\(f(n)\) 为钦定选 \(n\) 个物品的方案数,\(g(n)\) 为恰好选 \(n\) 个物品的方案数,自然有以下关系成立。
解释:钦定了 \(n\) 个,实际上可能得选法就是选 \(n\) 个,\(n+1\) 个一直到 \(m\) 个,对于实际选了 \(i\) 个,考虑它钦定的部分,用 \(\binom{i}{n}\) 种选法,所以贡献就是 \(\binom{i}{n}g(i)\)。

根据数学推导,可以得到
应用
根据 \(f,g\) 函数的实际意义,题目一般要求“恰好”的情况,设计出“钦定”怎么算后就可以反演直接求得。
P4071 [SDOI2016] 排列计数
令 \(f(k)\) 表示钦定 \(k\) 个位置满足 \(p_i=i\) 的方案数。
有 \(f(k) = \binom{n}{k} (n-k)! = \frac{n!}{k!}\)。
反演得到
\(g(m)\) 就是答案。
P1595 信封问题
这是一个错位排列问题,跟上一题是一样的,\(g(0)\) 就是答案。
P4859 已经没有什么好害怕的了
题目大意:给定 \(a,b\),求出 \(a_i>b_i\) 恰好有 \(k\) 对的方案数。
令 \(f(k)\) 表示钦定 \(k\) 对 \(a_i>b_i\) 的方案数,这个不好用组合数算,考虑 dp。
令 \(dp_{i,j}\) 表示从前 \(a\) 的前 \(i\) 个中选出 \(j\) 对 \(a_i>b_i\) 的方案数,有
其中 \(c_i\) 表示 \(b\) 中比 \(a_i\) 小的数的个数。
这个方程说的是,\(dp_{i,j}\) 可以从选 \(i\) 和不选 \(i\) 转移过来,如果选了 \(i\),那么只有 \(c_i-j+1\) 个 \(b\) 中的数可供选择。
dp 完后,有 \(f(k) = (n-i)!dp_{n,i}\) ,表示对剩下没有钦定的任意分配。
最后二项式反演得到 \(g(k)\) 即可。
#include <bits/stdc++.h>
#define int long long
#ifndef ONLINE_JUDGE
#include <debug.h>
#endif
#define pii pair<int,int>
#define all(x) x.begin(),x.end()
#define ull unsigned long long
#define uint unsigned int
#define rg register
#define il inline
#define rep(i,a,b) for(rg int i=(a);i<=(b);++i)
#define sqr(x) ((x)*(x))
using namespace std;
const int INF=0x3f3f3f3f;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
w=(w<<1)+(w<<3)+(ch^48);
ch=getchar();
}
return w*f;
}
inline void write(int x)
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=2005,mod=1e9+9;
int n,a[N],b[N],k,c[N],dp[N][N],frac[N],f[N],inv[N];
il int qpow(int a,int b,int mod)
{
int res=1;
while(b)
{
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
void init(int n)
{
frac[0]=inv[0]=1;
for(int i=1;i<=n;++i)
{
frac[i]=frac[i-1]*i%mod;
inv[i]=qpow(frac[i],mod-2,mod);
}
}
int C(int n,int m)
{
if(n<m) return 0;
return (frac[n]*inv[m]%mod)*inv[n-m]%mod;
}
signed main()
{
#ifndef ONLINE_JUDGE
//freopen("in.txt","r",stdin);
#endif
n=read(),k=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) b[i]=read();
if((n+k)%2!=0)
{
puts("0");
return 0;
}
k=(n+k)/2;
init(n);
sort(a+1,a+n+1),sort(b+1,b+n+1);
for(int i=1;i<=n;++i)
{
c[i]=upper_bound(b+1,b+n+1,a[i])-b-1;
}
dp[0][0]=1;
for(int i=1;i<=n;++i)
{
dp[i][0]=1;
for(int j=1;j<=n;++j)
{
(dp[i][j]=dp[i-1][j]+(c[i]-(j-1))*dp[i-1][j-1]%mod)%=mod;
}
}
for(int i=1;i<=n;++i)
{
f[i]=frac[n-i]*dp[n][i]%mod;
// cerr<<f[i]<<" ";
}
int ans=0;
for(int i=k;i<=n;++i)
{
ans+=((i-k)&1?-1:1)*f[i]*C(i,k);
ans%=mod;
}
cout<<(ans+mod)%mod<<endl;
return 0;
}

浙公网安备 33010602011771号