二项式反演学习笔记
二项式反演
二项式反演用于解决“某个物品恰好若干个”这类计数问题。
二项式定理
根据上式,我们不妨令 $ a=1,b=-1 $ ,那么可得推论式:
组合数公式
-
通项式:$ \binom{n}{m}=\frac{n!}{(n-m)!m!} $
-
递推式:$ \binom{n}{m}=\binom{n-1}{m}+\binom{n-1}{m-1} $
-
分离式:$ \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) $ 从 “至多” 变成了 “至少”。
证明同上,本文不再赘述。
总结
二项式反演可以归结于以下两个式子:
形式一
令 $ f(n) $ 表示至多 $ n $ 个/种的方案数量,$ g(n) $ 表示恰好 $ n $ 个/种的方案数量
形式二
令 $ f(n) $ 表示至少 $ n $ 个/种的方案数量,$ g(n) $ 表示恰好 $ n $ 个/种的方案数量
例题
题意:$ n $ 封信和$ n $ 封信封,求全部装错的方案数
思路:
不妨设 $ f(n) $ 为至多 $ n $ 个装错的方案数,$ g(n) $ 为恰好 $ n $ 个装错的方案数。
会发现对于 $ f(n) $ 包含了所有排列,即全排列,有:$ f(n)=n! $
那么有恒等式:
输出 $ 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;
}
- 染色问题
题意:有 $ 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 $ 种颜色的方案数,直接带入反演求解即可,这里不多赘述。
题意:给出 $ 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 $ 的数量,则有:
题目即求配对后让 $ 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 $ ,不难得出有转移方程:
举例:
对于 $ a={2,4,6},b={1,3,5} $ ,有:
那么怎么求得 $ f(i) $ 呢,其表示至少有 $ i $ 组使得 $ a>b $ 的方案数。而对于我们的 $ dp_{i,j} $ 剩下的 $ i-j $ 个配对是任意的,所以有关系式:
那么对于恰好选择 $ m $ 组使得 $ a>b $ 的方案数 $ g(m) $ 有如下反演:
代码:
#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;
}
题意:给定 $ n,k $ 。令 $ S_0={1,2,\ldots,n},S_1={S|S \subseteq S_0} $ ,求有多少 $ S_1 $ 的子集 $ S $ 使得其交的模为 $ k $ 。
我们设 $ f(k) $ 表示至少 $ k $ 个元素是集合的交集的方案数,即钦定 $ k $ 个元素作为集合的交集,剩余不做任何限制的方案数,会有重复,则:
设 $ g(k) $ 表示恰好有 $ k $ 个元素是集合的交集的方案数,有二项式反演:
求出 $ 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;
}
浙公网安备 33010602011771号