二项式反演学习笔记
前置芝士
二项式定理
、
组合数的性质:
将选出的集合对全集取补集,数值不变:
。
根据定义可以推出:
。
用杨辉三角的表达式,可以推出:
。
取二项式定理中 的特殊情况,可以得到:
。
同理,取二项式定理中 的特殊情况,可以得到:
。
拆式子,感性理解一下可以得到:
。
取 中 的特殊情况,可以得到:
。
通过对 所对应的多项式函数求导,可以得到:
。
在 中选出 个元素,可以考虑枚举最大的元素的值,那么就得到等式:
。
在 个元素中选取 个元素,再在 中选取 个元素,等价于先选 个元素,再在剩下的 个元素中选取 个元素:
。
而从杨辉三角上也不难发现:
。
其中 为斐波那契数列。
二项式反演
记 表示恰好使用 个不同元素形成特定结构的方案数, 表示从 个不同元素中选出 个元素形成特定结构的总方案数。
形式一:
形式二:
上述已知 求 的过程,即被称为二项式反演。
证明:
此处证明形式一。
将反演的公式中的 展开,得到:
交换枚举顺序,得到:
利用前面得到的公式 ,可以得到:
令 ,则 $i=k+j,上式转换为:
对后半部分式子利用公式 ,可以得到:
。
证毕。
已经没有什么好害怕的了
给出两个长度均为 的序列 和 ,保证这 个数互不相同。现要将 序列中的数与 序列中的数两两配对,求 “ 的对数比 的对数恰好多 ” 的配对方案数模 。
,。
思路
将题意转换一下,也就是求 的对数恰好为 。
将 和 从小到大排序,设 表示配对到 数组中的第 个数时,已经钦定了 对 的方案数。
设 表示 中有 个数小于 ,那么分是否要钦定当前的数进行转移:
。
最终 就为至少有 对 的方案数。记为 。
令 为恰好有 对 的方案数。那么就有:
。
通过二项式反演,可以得到:
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 正在玩一个游戏:有一棵包含 个点的有根树(点从 编号),它的根是 号点,初始时两人各拥有 个点。游戏的每个回合两人都需要选出一个自己拥有且之前未被选过的点,若对手的点在自己的点的子树内,则该回合自己获胜;若自己的点在对方的点的子树内,该回合自己失败;其他情况视为平局。游戏共进行 回合。
为对于 ,计算出非平局回合数为 的情况数。两种情况不同当且仅当存在一个小 A 拥有的点 ,小 B 在 被小 A 选择的那个回合所选择的点不同。
答案对 取模。
。
思路:
考虑设 为存在 回合非平局的方案数, 为恰好有 回合非平局的方案数。不难得到 的表达式:
。
根据二项式反演,得到 的表达式:
。
设 表示在以 为根的子树中存在 个非平局的回合数。简单转移一下即可。
最终 。
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】