二项式反演学习笔记

二项式反演

二项式反演用于解决“某个物品恰好若干个”这类计数问题。

二项式定理

\[(a+b)^n=\sum\limits_{i=0}^n\binom{n}{i}a^{n-i}b^i \]

根据上式,我们不妨令 $ a=1,b=-1 $ ,那么可得推论式

\[\sum\limits_{i=0}^n(-1)^i\binom{n}{i}=[n=0] \]

组合数公式

  1. 通项式:$ \binom{n}{m}=\frac{n!}{(n-m)!m!} $

  2. 递推式:$ \binom{n}{m}=\binom{n-1}{m}+\binom{n-1}{m-1} $

  3. 分离式:$ \binom{n}{k}\binom{k}{m}=\binom{n}{m}\binom{n-m}{k-m},n \geq k \geq m$

    证明:我们对左侧进行恒等变换:

    \[\begin{align} \binom{n}{k}\binom{k}{m}=&\frac{n!}{k!(n-k)!}\frac{k!}{m!(k-m)!} \\ =&\frac{n!}{m!(n-m)!}\frac{(n-m)!}{(k-m)!(n-k)!}\\ =&\binom{n}{m}\binom{n-m}{k-m} \end{align} \]

二项式反演

不妨设函数 $ f(n) $ 代表至多满足条件($ \leq n \()的方案数,\) g(n) $ 代表恰好满足条件($ =m $)的方案数 ,则有以下等式:

\[f(n)=\sum\limits_{i=0}^n\binom{n}{i}g(i) \Longleftrightarrow g(n)=\sum\limits_{i=0}^n(-1)^{n-i}\binom{n}{i}f(i) \]

证明:

\[\begin{align} \sum\limits_{i=0}^n(-1)^{n-i}\binom{n}{i}f(i)=&\sum\limits_{i=0}^n(-1)^{n-i}\binom{n}{i}\sum\limits_{j=0}^i\binom{i}{j}g(j)\\ =&\sum\limits_{i=0}^n\sum\limits_{j=0}^i(-1)^{n-i}\binom{n}{i}\binom{i}{j}g(j)\\ =&\sum\limits_{j=0}^n\sum\limits_{i=j}^n(-1)^{n-i}\binom{n}{i}\binom{i}{j}g(j)\\ =&\sum\limits_{j=0}^n\sum\limits_{i=j}^n(-1)^{n-i}\binom{n}{j}\binom{n-j}{i-j}g(j)\\ =&\sum\limits_{j=0}^n\binom{n}{j}g(j)\sum\limits_{i=j}^n(-1)^{n-i}\binom{n-j}{i-j}\\ =&\sum\limits_{j=0}^n\binom{n}{j}g(j)\sum\limits_{i=0}^{n-j}(-1)^{n-j-i}\binom{n-j}{i}\\ =&\sum\limits_{j=0}^n\binom{n}{j}g(j)[n-j=0]\\ =&\sum\limits_{j=0}^n\binom{n}{j}g(j)[n=j]\\ =&g(n) \end{align} \]

证毕。

除此之外,二项式反演还有另一种形式,在该形式下 $ f(n) $ 从 “至多” 变成了 “至少”。

\[f(n)=\sum\limits_{i=n}^m\binom{i}{n}g(i) \Longleftrightarrow g(n)=\sum\limits_{i=n}^m(-1)^{i-n}\binom{i}{n}f(i),n \leq m \]

证明同上,本文不再赘述。

总结

二项式反演可以归结于以下两个式子:

形式一

令 $ f(n) $ 表示至多 $ n $ 个/种的方案数量,$ g(n) $ 表示恰好 $ n $ 个/种的方案数量

\[f(n)=\sum\limits_{i=0}^n\binom{n}{i}g(i) \Longleftrightarrow g(n)=\sum\limits_{i=0}^n(-1)^{n-i}\binom{n}{i}f(i) \]

形式二

令 $ f(n) $ 表示至少 $ n $ 个/种的方案数量,$ g(n) $ 表示恰好 $ n $ 个/种的方案数量

\[f(n)=\sum\limits_{i=n}^m\binom{i}{n}g(i) \Longleftrightarrow g(n)=\sum\limits_{i=n}^m(-1)^{i-n}\binom{i}{n}f(i),n \leq m \]

例题

  1. P1595 信封问题 - 洛谷

题意:$ n $ 封信和$ n $ 封信封,求全部装错的方案数

思路:

不妨设 $ f(n) $ 为至多 $ n $ 个装错的方案数,$ g(n) $ 为恰好 $ n $ 个装错的方案数。

会发现对于 $ f(n) $ 包含了所有排列,即全排列,有:$ f(n)=n! $

那么有恒等式:

\[\begin{align} f(n)=&\sum\limits_{i=0}^n\binom{n}{i}g(i)\\ g(n)=&\sum\limits_{i=0}^n(-1)^{n-i}\binom{n}{i}i!\\ =&\sum\limits_{i=0}^n(-1)^{n-i}\frac{n!}{i!(n-i)!}i!\\ =&\sum\limits_{i=0}^n\frac{(-1)^in!}{i!} \end{align} \]

输出 $ g(n) $ 即可。

代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int maxn=100;
int n,fac[maxn];

void solve()
{
	cin>>n;
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i;
	int ans=0;
	for(int i=0;i<=n;i++) 
	{
		if(i&1) ans-=fac[n]/fac[i];
		else ans+=fac[n]/fac[i];
	}
	cout<<ans<<endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int t=1;
//	cin>>t;
	while(t--)
	{
		solve();
	}
	
	
	
	
	return 0;
}
  1. 染色问题

题意:有 $ n $ 个格子、$ m $ 种颜色,你需要给每个格子都染上某一种颜色且相邻的格子不可以同色。

对于染色问题有线性染色问题、中心不涂色环形染色问题、中心涂色环形染色问题。下面分别给各自的方案数:

线性染色问题:用 $ f_m $ 表示相邻格子不同色、任意涂色(至多使用 $ m $ 种颜色)的方案数,则 \(f_m=m(m-1)^{n-1}\)

中心不涂色环形染色问题:用 $ f_m $ 表示相邻格子不同色、任意涂色(至多使用 $ m $ 种颜色)的方案数,则 \(f_m=(m-1)^{n}+(-1)^n(m-1)\)

中心涂色环形染色问题:用 $ f_m $ 表示相邻格子不同色、任意涂色(至多使用 $ m $ 种颜色)的方案数,则

\(f_m=(m+1)[(m-1)^n+(-1)^n(m-1)]\)

若题设求问恰好使用 $ m $ 种颜色的方案数,直接带入反演求解即可,这里不多赘述。

  1. P4859 已经没有什么好害怕的了 - 洛谷

题意:给出 $ n $ 个数 $ a_i $ 以及 $ n $ 个数 $ b_i $ 并且保证 $ a,b $ 无相同元素,要求两两配对使得 $ a>b $ 的对数减去 $ a<b $ 的对数恰好等于 $ k $ ,求配对的方案数。

思路:由于保证 $ a,b $ 无相同元素,那么对于任意 $ i,j \in {1,2,\ldots,n} $ ,对应的 $ a_i,b_j $ 只会存在 $ a_i < b_j $ 或 $ a_i>b_j $ 两种关系,那么记 $ num_1 $ 表示配对后 $ a>b $ 的数量,$ num_2 $ 为配对后 $ a<b $ 的数量,则有:

\[\begin{align} num_1+num_2=n,num_1=num_2+k \rightarrow num_1=\frac{n+k}{2} \end{align} \]

题目即求配对后让 $ num_1 $ 恰好等于 $ \frac{n+k}{2} $ 的方案数。

我们现在考虑如何求解至少选择 $ i $ 组使得 $ a>b $ 的方案数,记为 $ f(i) $ ,剩下的 $ n-i $ 组随意。

先让 $ a_i,b_i $ 进行排序,那么有 $ \forall i \in {1,2,\ldots,n-1},a_i<a_{i+1},b_i<b_{i+1} $ 。

若 $ b_i <a_1<b_{i+1},b_j<a_2<b_{j+1} (i<j)$ ,那么 $ a_1 $ 可以对应 $ b_{1,2,\ldots,i} $ ,$ a_2 $ 可以对应 $ b_{1,2,\ldots,j} $ ,若 $ a_1 $ 对应的 $ b_x $ 满足 $ x \leq i <j $ ,所以必然占据一个 $ a_2 $ 可以对应的位置,所以共有 $ i(j-1) $ 种对应方法。

不妨设 $ dp_{i,j} $ 表示只考虑 $ a $ 数组的前 $ i $ 个元素,这 $ i $ 个元素都进行配对之后,有 $ j $ 个配对是满足大于关系的,这些满足大于要求的配对的方案数,令 $ p(i) $ 表示 $ b $ 数组种最大的小于 $ a_i $ 的下标(即满足 $ b_{p(i)} <a_i<b_{p(i)+1} $),初始化 $ dp_{0,0}=1 $ ,不难得出有转移方程:

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

举例:

对于 $ a={2,4,6},b={1,3,5} $ ,有:

\[dp_{2,1}=3:\{2,1\},\{4,1\},\{4,3\}\\ dp_{2,2}=1:\{2,1\ 4,3\}\\ dp_{3,2}=7:\{2,1 \ 6,3\}\{2,1\ 6,5\}\{4,1\ 6,3\}\{4,1 \ 6,5\}\{4,3\ 6,1\}\{4,3\ 6,5\}+\{2,1 \ 4,3\} \]

那么怎么求得 $ f(i) $ 呢,其表示至少有 $ i $ 组使得 $ a>b $ 的方案数。而对于我们的 $ dp_{i,j} $ 剩下的 $ i-j $ 个配对是任意的,所以有关系式:

\[f(i)=dp_{n,i}\times (n-i)! \]

那么对于恰好选择 $ m $ 组使得 $ a>b $ 的方案数 $ g(m) $ 有如下反演:

\[\begin{align} g(m)=&\sum\limits_{i=m}^n(-1)^{i-m}\binom{i}{m}f(i)\\ =&\sum\limits_{i=m}^n(-1)^{i-m}\binom{i}{m}dp_{n,i}(n-i)! \end{align} \]

代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int mod=1e9+9;
const int maxn=2010;
int n,k,a[maxn],b[maxn],dp[maxn][maxn],C[maxn][maxn],p[maxn],f[maxn];

void solve()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	sort(a+1,a+1+n);
	sort(b+1,b+1+n);
	if((n+k)&1)
	{
		cout<<0<<endl;
		return ;
	}
	k=(n+k)>>1;
	for(int i=0;i<=n;i++) C[i][0]=1;
	for(int i=1;i<=n;i++) for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	for(int i=0;i<=n;i++) dp[i][0]=1;
	for(int i=1;i<=n;i++) p[i]=upper_bound(b+1,b+1+n,a[i])-b-1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
		  dp[i][j]=(dp[i-1][j]+(dp[i-1][j-1]*(p[i]-j+1))%mod)%mod;
	for(int j=n,s=1;j;j--) 
	{
		f[j]=(dp[n][j]*s)%mod;
		s=s*(n-j+1)%mod;
	}
	int ans=0;
	for(int i=k;i<=n;i++)
	{
		int term=C[i][k]*f[i]%mod;
		if((i-k)&1) ans=(ans-term+mod)%mod;
		else ans=(ans+term)%mod;
	}
	cout<<ans<<endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int t=1;
//	cin>>t;
	while(t--)
	{
		solve();
	}
	
	
	
	
	return 0;
 } 
  1. P10596 BZOJ2839 集合计数 - 洛谷

题意:给定 $ n,k $ 。令 $ S_0={1,2,\ldots,n},S_1={S|S \subseteq S_0} $ ,求有多少 $ S_1 $ 的子集 $ S $ 使得其交的模为 $ k $ 。

我们设 $ f(k) $ 表示至少 $ k $ 个元素是集合的交集的方案数,即钦定 $ k $ 个元素作为集合的交集,剩余不做任何限制的方案数,会有重复,则:

\[f(k)=\binom{n}{k}(2^{2^{n-k}}-1) \]

设 $ g(k) $ 表示恰好有 $ k $ 个元素是集合的交集的方案数,有二项式反演:

\[\begin{align} g(k)=&\sum_{i=k}^n(-1)^{i-k}\binom{i}{k}f(i)\\ =&\sum_{i=k}^n(-1)^{i-k}\binom{i}{k}\binom{n}{i}(2^{2^{n-i}}-1) \end{align} \]

求出 $ g(k) $ 即可。

代码:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int mod=1e9+7;
const int maxn=1e6+10;
int fac[maxn],inv[maxn];
int n,k;

int C(int n,int m)
{
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}

int fast_pow(int a,int b,int mod)
{
	a%=mod;
	int res=1;
	while(b)
	{
		if(b&1) res=(res*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return res;
}

int Inv(int a,int mod)
{
	return fast_pow(a,mod-2,mod);
}

void solve()
{
	cin>>n>>k;
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=(fac[i-1]*i)%mod;
	inv[0]=1;
	inv[n]=Inv(fac[n],mod);
	for(int i=n-1;i>=1;i--) inv[i]=(inv[i+1]*(i+1))%mod;
	int a=2;
	int ans=0;
	for(int i=n;i>=k;i--)
	{
		int term=C(i,k)*(a-1)%mod*C(n,i)%mod;
		if((i-k)&1) term*=-1;
		ans=(ans+term+mod)%mod;
		a=(a*a)%mod;
	}
	cout<<ans<<endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int t=1;
//	cin>>t;
	while(t--)
	{
		solve();
	}
	
	
	
	
	return 0;
 } 
posted on 2025-08-04 22:28  Linear_L  阅读(24)  评论(0)    收藏  举报