二项式反演

什么是反演

已知 \(g_i=a_i \cdot f_i\) ,推出 \(f_i\) 就是反演

二项式反演公式

  1. \(\displaystyle g_n=\sum_{i=0}^{n}{(-1)^i\binom{n}{i}f_i \Leftrightarrow f_i=\sum_{i=0}^{n}{(-1)^i}\binom{n}{i}g_i}\)
  2. \(\displaystyle g_n=\sum_{i=0}^{n}\binom{n}{i}f_i \Leftrightarrow f_n=\sum_{i=0}^{n}(-1)^{n-i}\binom{n}{i}g_i\)
  3. \(\displaystyle g_m=\sum_{i=m}^{n}(-1)^i\binom{i}{m}f_i \Leftrightarrow f_m=\sum_{i=m}^{n}{(-1)^i\binom{i}{m}g_i}\)
  4. \(\displaystyle g_m=\sum_{i=m}^{n}{\binom{i}{m}f_i} \Leftrightarrow f_m=\sum_{i=m}^{n}{(-1)^{i-m}\binom{i}{m}g_i}\)

证明:文章 (感觉证明没那么重要)

例#1:BZOJ2839 集合计数

题目:

一个有 \(N\) 个元素的集合有 \(2^N\) 个不同子集(包含空集),现在要在这 \(2^N\) 个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 \(K\),求取法的方案数,答案模 \(10^9+7\)。

\(1\leq N\leq 1000000\),\(0\leq K\leq N\)。

分析:

这是较为经典的用 “指定至少某 \(i\) 个” 向 “恰好 \(i\) 个转化”。设 \(g_i\) 表示指定 \(i\) 个元素(如果指定不同就是不同的方案),所有集合的交集至少包含这 \(i\) 个元素的方案数。选择 \(i\) 个,是 \(\displaystyle \binom{n}{i}\) ,包含这 \(i\) 个的方案数,是 \(\displaystyle 2^{2^{n-i}}\) 个(假定所有已选的集合包含这 \(i\) 个元素,其他任选)。设 \(f_i\) 表示恰好包含 \(i\) 个元素的方案数,则有 \(\displaystyle g_k=\sum_{i=k}^{n}{\binom{i}{k}f_i}\) 。根据二项式反演,有 \(\displaystyle f_k=\sum_{i=k}^{n}(-1)^{i-k}\binom{i}{k}g_i\) 。线性求出。

例#2:P4859 已经没有什么好害怕的了

题目:

这时,已经多次面对过 Charlotte 的 Homura 告诉了学 OI 的你这样一个性质:Charlotte 的结界中有两种具有能量的元素,一种是“糖果”,另一种是“药片”,各有 \(n\) 个。在 Charlotte 发动进攻前,“糖果”和“药片”会两两配对,若恰好“糖果”比“药片”能量大的组数比“药片”比“糖果”能量大的组数多 \(k\) 组,则在这种局面下,Charlotte 的攻击会丟失,从而 Mami 仍有消灭 Charlotte 的可能。

你必须根据 Homura 告诉你的“糖果”和“药片”的能量的信息迅速告诉 Homura 这种情况的个数.

一个答案,表示消灭 Charlotte 的情况个数,需要对 \(10^9+9\) 取模。

对于 \(100\%\) 的数据,\(1 \le n \le 2000\),\(0 \le k \le n\),\(1\le a_i,b_j\le 10^9\)。保证输入的 \(2n\) 个整数值 \(a_i,b_j\) 两两不同。

分析:

设 \(\displaystyle m=\frac{n+k}{2}\) 。分析可知,多 \(k\) 组就是 \(a>b\) 共有 \(m\) 组。显然 \(m\) 不是整数的时候方案数为 \(0\) 。恰好为 \(m\) 组,比较适合用二项式反演。设 \(f_m\) 表示恰好有 \(m\) 组,\(g_m\) 表示指定的 \(m\) 对一定满足 \(a>b\) ,其余对随意。那么 \(\displaystyle g_m=\sum_{i=m}^{n}{\binom{i}{m}f_i}\) ,则 \(\displaystyle f_m=\sum_{i=m}^{n}{(-1)^{i-m}\binom{i}{m}g_i}\) 。现在就是要用其他方法在 \(n^2\) 的时间内算出 \(g_i\) 。这个计数还是用dp更好。先将 \(a,b\) 排序。设 \(p_i\) 表示有多少个 \(b\) 小于 \(a_i\) (这个用双指针可以算)。设 \(h_{l,i}\) 表示在前 \(i\) 个 \(a_i\) 中,以 \(a_i\) 结尾的配 \(l\) 对方案数。显然前面配了 \(l-1\) 对,现在 \(a_i\) 就还有 \(p_i-(l-1)\) 种方案。自然 \(\displaystyle h_{l,i}=(\sum_{j=1}^{i-1}{h_{l,j}}) \cdot [p_i-(l-1)]\) 。方程中 \(h\) 的求和那一部分可以用前缀和优化,做到 \(n^2\) 。如此,\(\displaystyle g_l=(n-l)! \ \cdot \sum_{i=1}^{n}{h_{l,i}}\) 。

例#3:P6478 [NOI Online #2 提高组] 游戏

题目:

小 A 和小 B 正在玩一个游戏:有一棵包含 \(n=2m\) 个点的有根树(点从 \(1\sim n\) 编号),它的根是 \(1\) 号点,初始时两人各拥有 \(m\) 个点。游戏的每个回合两人都需要选出一个自己拥有且之前未被选过的点,若对手的点在自己的点的子树内,则该回合自己获胜;若自己的点在对方的点的子树内,该回合自己失败;其他情况视为平局。游戏共进行 \(m\) 回合。

对于 \(k=0,1,2,\cdots,m\),计算出非平局回合数为 \(k\) 的情况数。两种情况不同当且仅当存在一个小 A 拥有的点 \(x\),小 B 在 \(x\) 被小 A 选择的那个回合所选择的点不同。

由于情况总数可能很大,你只需要输出答案对 \(998244353\) 取模后的结果。\(1 \le n \le 5000\)

分析:

二项式反演。设 \(ans_i\) 表示恰好有 \(i\) 局非平局,\(g_i\) 表示指定 \(i\) 局非平局,其余随意,则有 \(\displaystyle g_i=\sum_{j=i}^{m}{\binom{j}{i}f_j}\) ,则 \(\displaystyle f_i=\sum_{j=i}^{m}{(-1)^{j-i}\binom{j}{i}g_j}\) 。怎么求 \(g_j\) ?

这显然是个树上dp。设 \(f_{x,i}\) 表示在子树 \(x\) 内指定 \(i\) 对点的方案数。按照背包计数的方式转移。如果选择当前节点 \(x\) ,且子树内有 \(i\) 对,那就有 \(\displaystyle s_{x,col_x \oplus 1} -(i-1)\) 种可以和 \(x\) 配对。( \(s_{i,0}\) 表示在 \(i\) 子树内有 \(s_{i,0}\) 个为小 A 所有)。显然 \(\displaystyle g_j=f_{1,j} \ \cdot (m-j) !\) 。背包的写法还是要优化的,可以达到 \(O(n^2)\) 。

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5005;
const ll mod=998244353;
int n,m;
int col[N];
vector<int>v[N];
int s[N][2];
int siz[N];
ll f[N][N];
void dfs(int x,int fa){
    s[x][0]+=(col[x]==0);
    s[x][1]+=(col[x]==1);
    siz[x]+=1;
    for(auto i:v[x]){
        if(i==fa)continue;
        dfs(i,x);
        for(int j=min(m,(siz[x]+siz[i])>>1);j>=1;j--){
            for(int k=max(1,j-(siz[x]>>1));k<=min(j,siz[i]>>1);k++){
                f[x][j]+=(f[x][j-k]*f[i][k])%mod;
                f[x][j]%=mod;
            }
        }
        siz[x]+=siz[i];
        s[x][0]+=s[i][0],s[x][1]+=s[i][1];
    }
    for(int i=min(m,siz[x]>>1);i>=1;i--){
        f[x][i]+=max(0ll,f[x][i-1]*(s[x][col[x]^1]-(i-1)))%mod;
        f[x][i]%=mod;
    }
}
ll fac[N],C[N][N];
int main(){
    scanf("%d",&n);
    m=n>>1;
    for(int i=1;i<=n;i++)scanf("%1d",&col[i]);
    for(int i=1;i<=n-1;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        v[x].emplace_back(y);
        v[y].emplace_back(x);
    }
    for(int i=1;i<=n;i++)f[i][0]=1;
    dfs(1,0);
    fac[0]=1;
    for(int i=1;i<=m;i++){
        fac[i]=(fac[i-1]*i)%mod;
    }
    for(int i=0;i<=m;i++)C[i][0]=1;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=m;j++){
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        }
    }
    for(int i=0;i<=m;i++){
        ll ans=0,flag=1;
        for(int j=i;j<=m;j++){
            ll val=(f[1][j]*fac[m-j])%mod;
            ans+=(flag*(C[j][i]*val)%mod+mod)%mod;
            ans%=mod;
            flag*=-1;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2026-09-27 17:45  TanHaoren  阅读(8)  评论(0)    收藏  举报