二项式反演学习笔记

前置芝士

二项式定理

\((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;
}
posted @ 2023-03-09 15:42  曙诚  阅读(50)  评论(0)    收藏  举报