二项式反演学习笔记

形式

考虑 \(m\) 个有标号的物品。

\(f(n)\) 为钦定选 \(n\) 个物品的方案数,\(g(n)\) 为恰好选 \(n\) 个物品的方案数,自然有以下关系成立。

\[f(n) = \sum_{i=n}^{m} \binom{i}{n} g(i) \]

解释:钦定了 \(n\) 个,实际上可能得选法就是选 \(n\) 个,\(n+1\) 个一直到 \(m\) 个,对于实际选了 \(i\) 个,考虑它钦定的部分,用 \(\binom{i}{n}\) 种选法,所以贡献就是 \(\binom{i}{n}g(i)\)

根据数学推导,可以得到

\[g(n) = \sum_{i=n}^{m} \binom{i}{n} (-1)^{i-n}f(i) \]

应用

根据 \(f,g\) 函数的实际意义,题目一般要求“恰好”的情况,设计出“钦定”怎么算后就可以反演直接求得。

P4071 [SDOI2016] 排列计数

\(f(k)\) 表示钦定 \(k\) 个位置满足 \(p_i=i\) 的方案数。

\(f(k) = \binom{n}{k} (n-k)! = \frac{n!}{k!}\)

反演得到

\[g(k) = \sum_{i=k}^{n} \binom{i}{k} (-1)^{i-k} f(i) \]

\(g(m)\) 就是答案。

P1595 信封问题

这是一个错位排列问题,跟上一题是一样的,\(g(0)\) 就是答案。

\[g(0) = \sum_{i=0}^{n}\frac{(-1)^{i} n!}{i!} \]

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\) 的方案数,有

\[dp_{i,j} = dp_{i-1,j} + (c_i - j + 1) dp_{i-1,j-1} \]

其中 \(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;
}
posted @ 2025-04-22 10:10  vanueber  阅读(23)  评论(0)    收藏  举报