二项式反演学习笔记
前置芝士
二项式定理
\((a+b)^n=\sum_{i=0}^{n}\binom{n}{i}a^ib^{n-i}\)、
组合数的性质:
将选出的集合对全集取补集,数值不变:
\(\binom{n}{m}=\binom{n}{n-m}\) 。 \((1)\)
根据定义可以推出:
\(\binom{n}{k}=\frac{n}{k} \binom{n-1}{k-1}\) 。 \((2)\)
用杨辉三角的表达式,可以推出:
\(\binom{n}{m}=\binom{n-1}{m-1}+\binom{n-1}{m}\)。 \((3)\)
取二项式定理中 \(a=b=1\) 的特殊情况,可以得到:
\(\binom{n}{0}+\binom{n}{1}+...+\binom{n}{n}=\sum_{i=0}^{n} \binom{n}{i}=2^n\)。\((4)\)
同理,取二项式定理中 \(a=1,b=-1\) 的特殊情况,可以得到:
\(\sum_{i=0}^{n}(-1)^i\binom{n}{i}=[n==0]\)。\((5)\)
拆式子,感性理解一下可以得到:
\(\sum_{i=0}^{m} \binom{n}{i} \binom{m}{m-i}=\binom{n+m}{m}\)。\((6)\)
取 \((6)\) 中 \(n=m\) 的特殊情况,可以得到:
\(\sum_{i=0}^{n} \binom{n}{i}^2=\binom{2n}{n}\)。\((7)\)
通过对 \((4)\) 所对应的多项式函数求导,可以得到:
\(\sum_{i=0}^{n} i \binom{n}{i}=n2^{n-1}\)。\((8)\)
在 \(1\sim {n+1}\) 中选出 \(k+1\) 个元素,可以考虑枚举最大的元素的值,那么就得到等式:
\(\sum_{i=0}^{n} \binom{i}{k}=\binom{n+1}{k+1}\)。\((9)\)
在 \(n\) 个元素中选取 \(r\) 个元素,再在 \(r\) 中选取 \(k\) 个元素,等价于先选 \(k\) 个元素,再在剩下的 \(n-k\) 个元素中选取 \(r-k\) 个元素:
\(\binom{n}{r}\binom{r}[k]=\binom{n}{k}\binom{n-k}{r-k}\)。\((10)\)
而从杨辉三角上也不难发现:
\(\sum_{i=0}^{n}\binom{n-i}{i}=F_{n+1}\)。
其中 \(F\) 为斐波那契数列。
二项式反演
记 \(f_n\) 表示恰好使用 \(n\) 个不同元素形成特定结构的方案数,\(g_n\) 表示从 \(n\) 个不同元素中选出 \(i \geq 0\) 个元素形成特定结构的总方案数。
形式一:
\(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)\)
形式二:
\(g(n)=\sum_{i=n}^{m} \binom{i}{n}f(i) \Leftrightarrow f(n)=\sum_{i=n}^{m}(-1)^{i-n}\binom{i}{n}g(i)\)
上述已知 \(g_n\) 求 \(f_n\) 的过程,即被称为二项式反演。
证明:
此处证明形式一。
将反演的公式中的 \(g_i\) 展开,得到:
$f_n=\sum_{i=0}^{n}\binom{n}{i} (-1)^{n-i} \left [ \sum_{j=0}^{i} \binom{i}{j} f_j\right ] $
\(=\sum_{i=0}^{n} \sum_{j=0}^{i} \binom{n}{i} \binom{i}{j} (-1)^{n-i} f_j\)
交换枚举顺序,得到:
\(f_n=\sum_{j=0}^{n} \sum_{i=j}^{n} \binom{n}{i} \binom{i}{j} (-1)^{n-i} f_j\)
$=\sum_{j=0}^{n} f_j \sum_{i=j}^{n} \binom{n}{i} \binom{i}{j} (-1)^{n-i} $
利用前面得到的公式 \((10)\),可以得到:
$f_n=\sum_{j=0}^{n} f_j \sum_{i=j}^{n} \binom{n}{j} \binom{n-j}{i-j} (-1)^{n-i} $
\(=\sum_{j=0}^{n} f_j \binom{n}{j} \sum_{i=j}^{n} \binom{n-j}{i-j} (-1)^{n-i}\)
令 \(k=i-j\),则 $i=k+j,上式转换为:
\(f_n=\sum_{j=0}^{n} f_j \binom{n}{j} \sum_{k=0}^{n-j} \binom{n-j}{k} (-1)^{n-j-k}\)
对后半部分式子利用公式 \((5)\),可以得到:
\(f_n=\sum_{j=0}^{n} f_j \binom{n}{j} [n==j]=f_n\)。
证毕。
已经没有什么好害怕的了
给出两个长度均为 \(n\) 的序列 \(A\) 和 \(B\) ,保证这 \(2n\) 个数互不相同。现要将 \(A\) 序列中的数与 \(B\) 序列中的数两两配对,求 “ \(A>B\) 的对数比 \(A<B\) 的对数恰好多 \(k\) ” 的配对方案数模 \(10^9+9\) 。
\(1 \le n \le 2000\),\(0 \le k \le n\)。
思路
将题意转换一下,也就是求 \(A>B\) 的对数恰好为 \(\dfrac{n+k}{2}\)。
将 \(A\) 和 \(B\) 从小到大排序,设 \(dp_{i,j}\) 表示配对到 \(A\) 数组中的第 \(i\) 个数时,已经钦定了\(j\) 对 \(A>B\) 的方案数。
设 \(cnt_{i}\) 表示 \(B\) 中有 \(cnt_i\) 个数小于 \(A_i\),那么分是否要钦定当前的数进行转移:
\(dp_{i,j}=dp_{i-1,j}+dp_{i-1,j-1} \times (cnt_i-(j-1))\)。
最终 \(dp_{n,i} \times A_{n-i}^{n-i}\) 就为至少有 \(i\) 对 \(A>B\) 的方案数。记为 \(g(i)\)。
令 \(f(i)\) 为恰好有 \(i\) 对 \(A>B\) 的方案数。那么就有:
\(g_{m}=\sum_{i=m}^{n}\binom{i}{m}f(i)\)。
通过二项式反演,可以得到:
\(f(m)=\sum_{i=m}^{n} (-1)^{i-m} \binom{i}{m} g(i)=\sum_{i=m}^{n} (-1)^{i-m} \binom{i}{m} \times dp_{n,i} \times A_{n-i}^{n-i}\)
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2010,mod=1e9+9;
int n,k,f[N][N],c[N][N],ans,a[N],b[N],cnt[N],fac[N];
void add(int &a,int b){a+=b;if(a>=mod) a-=mod;}
void sub(int &a,int b){a-=b;if(a<0) a+=mod;}
int main()
{
scanf("%d%d",&n,&k);if(n+k&1) return puts("0"),0;k=(n+k)/2;fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<=n;i++) for(int j=0;j<=i;j++) if(!j) c[i][j]=1;else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);sort(a+1,a+n+1);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);sort(b+1,b+n+1);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cnt[i]+=(a[i]>b[j]);f[0][0]=1;
for(int i=1;i<=n;i++) for(int j=0;j<=i;j++) add(f[i][j],f[i-1][j]),add(f[i][j],1ll*f[i-1][j-1]*(cnt[i]-j+1)%mod);
for(int i=k;i<=n;i++) if(i-k&1) sub(ans,1ll*c[i][k]*f[n][i]%mod*fac[n-i]%mod);else add(ans,1ll*c[i][k]*f[n][i]%mod*fac[n-i]%mod);
printf("%d\n",ans);
return 0;
}
游戏
小 A 和小 B 正在玩一个游戏:有一棵包含 \(n=2m\) 个点的有根树(点从 \(1\sim n\) 编号),它的根是 \(1\) 号点,初始时两人各拥有 \(m\) 个点。游戏的每个回合两人都需要选出一个自己拥有且之前未被选过的点,若对手的点在自己的点的子树内,则该回合自己获胜;若自己的点在对方的点的子树内,该回合自己失败;其他情况视为平局。游戏共进行 \(m\) 回合。
为对于 \(k=0,1,2,\cdots,m\),计算出非平局回合数为 \(k\) 的情况数。两种情况不同当且仅当存在一个小 A 拥有的点 \(x\),小 B 在 \(x\) 被小 A 选择的那个回合所选择的点不同。
答案对 \(998244353\) 取模。
\(1 \leq n \leq 5000\)。
思路:
考虑设 \(g_i\) 为存在\(i\) 回合非平局的方案数,\(f_i\) 为恰好有\(i\) 回合非平局的方案数。不难得到 \(g\) 的表达式:
\(g(m)=\sum_{i=m}^{n} \binom{i}{m}f(i)\)。
根据二项式反演,得到 \(f\) 的表达式:
\(f(m)=\sum_{i=m}^{n} (-1)^{i-m}\binom{i}{m}g(i)\)。
设 \(dp_{i,j}\) 表示在以 \(i\) 为根的子树中存在 \(j\) 个非平局的回合数。简单转移一下即可。
最终 \(g_i=\sum_{j=i}^{m}\binom{j}{i}dp_{1,j} A_{m-j}^{m-j}\)。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5010,mod=998244353;
int tmp[N],siz[N],siz1[N],h[N],idx,n,f[N][N],fac[N],c[N][N];char s[N];
struct edge{int v,nex;}e[N<<1];
void add(int u,int v){e[++idx]=edge{v,h[u]};h[u]=idx;}
void Add(int &a,int b){a+=b;if(a>=mod) a-=mod;}
void Sub(int &a,int b){a-=b;if(a<0) a+=mod;}
void dfs(int u,int fa)
{
siz[u]=1;siz1[u]=s[u]-'0';f[u][0]=1;
for(int i=h[u];i;i=e[i].nex)
{
int v=e[i].v;if(v==fa) continue;dfs(v,u);
for(int j=0;j<=siz[u]+siz[v];j++) tmp[j]=0;
for(int j=0;j<=siz[u];j++) for(int k=0;k<=siz[v];k++) if(j+k<=n) Add(tmp[j+k],1ll*f[u][j]*f[v][k]%mod);
for(int j=0;j<=siz[u]+siz[v];j++) f[u][j]=tmp[j];
siz[u]+=siz[v];siz1[u]+=siz1[v];
}
for(int i=min(siz1[u],siz[u]-siz1[u]);i;i--)
if(s[u]=='1') Add(f[u][i],1ll*f[u][i-1]*(siz[u]-siz1[u]-(i-1))%mod);
else Add(f[u][i],1ll*f[u][i-1]*(siz1[u]-(i-1))%mod);
}
int main()
{
scanf("%d",&n);scanf("%s",s+1);for(int u,v,i=1;i<n;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u);fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<=n;i++) for(int j=0;j<=i;j++) if(!j) c[i][j]=1;else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;dfs(1,0);
for(int i=0;i<=n/2;i++)
{
int ans=0;
for(int j=i;j<=n/2;j++)
if(j-i&1) Sub(ans,1ll*c[j][i]*f[1][j]%mod*fac[n/2-j]%mod);
else Add(ans,1ll*c[j][i]*f[1][j]%mod*fac[n/2-j]%mod);
printf("%d\n",ans);
}
return 0;
}