反演基础

news/2025/3/18 20:42:02/文章来源:https://www.cnblogs.com/Ydoc770/p/18778305

前言

在 OI 中,所谓容斥思想,很多时候就是面对题目中的强性质不好直接表示时,先表示出一个易求的弱性质,再去考虑用强性质表示出弱性质,通过反演来得到用弱性质表示的强性质。

对于不同式子的反演方法各不相同,类似的变化也比较多样,下文将结合例题来探讨各种不同反演的应用。

目录
  • 前言
    • 二项式反演
      • 基本思想
      • 二项式反演
      • 证明
      • 推广
      • 应用
        • BZOJ 2839 集合计数
        • Luogu P4859 已经没有什么好害怕的了

二项式反演

基本思想

\(f_n\) 表示恰好使用 \(n\) 个不同元素形成的特定结构的方案数,\(g_n\) 表示从 \(n\) 个不同元素中选出若干个元素形成特定结构的总方案数。显然这里的 \(g_n\) 性质是弱于 \(f_n\) 的,因为 \(g_n\) 没有限制选出元素形成特定结构的元素个数。

若已知 \(f_n\)\(g_n\),考虑枚举 \(g_n\) 中元素的个数 \(i\),如果从 \(n\) 个元素中选 \(i\) 个,每种情况的方案数就是 \(f_i\),不难得到:

\[g_n=\sum_{i=0}^n{n\choose i}f_i \]

若已知 \(g_n\)\(f_n\),我们对上式使用容斥,得到:

\[f_n=\sum_{i=0}^n{n\choose i}(-1)^{n-i}g_i \]

上面容斥式子的本质就是二项式反演。

二项式反演

二项式反演的基本形式即

\[g_n=\sum_{i=0}^n{n\choose i}f_i\iff f_n=\sum_{i=0}^n{n\choose i}(-1)^{n-i}g_i \]

这个式子还有一个等价的对称形式

\[g_n=\sum_{i=0}^n(-1)^i{n \choose i}f_i\iff f_n=\sum_{i=0}^n{n\choose i}(-1)^ig_i \]

证明

我们考虑基本形式的证明,其它形式的数值证明是类似的。

对于 \(f_n\) 的式子,我们先把其中的 \(g_i\) 展开,有

\[\begin{aligned} f_n&=\sum_{i=0}^n{n\choose i}(-1)^{n-i}g_i\\ &=\sum_{i=0}^n{n\choose i}(-1)^{n-i}\sum_{j=0}^i{i\choose j}f_j \end{aligned} \]

交换求和次序,先枚举 \(j\),再考虑枚举每个 \(i\ge j\),有

\[\sum_{j=0}^nf_j\sum_{i=j}^n{n\choose i}{i\choose j}(-1)^{n-i} \]

运用三项式版恒等式,再把与 \(i\) 无关的项提到前面,有

\[\begin{aligned} &\sum_{j=0}^nf_j\sum_{i=j}^n{n\choose j}{n-j\choose i-j}(-1)^{n-i}\\ =&\sum_{j=0}^n{n\choose j}f_j\sum_{i=j}^n{n-j\choose i-j}(-1)^{n-i}\\ \end{aligned} \]

\(k=i-j\),枚举 \(k\) 代替枚举 \(i\),剩下的式子就是 \((1-1)^{n-j}\) 的二项式定理展开,故有

\[\begin{aligned} \sum_{i=0}^n{n\choose i}(-1)^{n-i}g_i&=\sum_{j=0}^n{n\choose j}f_j\sum_{k=0}^{n-j}{n-j\choose k}(-1)^{n-j-k}\\ &=\sum_{j=0}^n{n\choose j}f_j(1-1)^{n-j}\\ &=\sum_{j=0}^n{n\choose j}f_j[n-j=0]=f_n \end{aligned} \]

推广

从线性代数的角度来看,如果把 \(f_0,f_1,\cdots,f_n\)\(g_0,g_1,\cdots,g_n\) 写成列向量,则二项式反演可以写为

\( \begin{gather*}\underbrace{\begin{pmatrix}\binom{0}{0}&0&\cdots&0\\\binom{1}{0}&\binom{1}{1}&\cdots&0\\\vdots&\vdots&\ddots&\vdots\\\binom{n}{0}&\binom{n}{1}&\cdots&\binom{n}{n}\end{pmatrix}}_{\boldsymbol A}\begin{pmatrix}f_0\\f_1\\\vdots\\f_n\end{pmatrix}=\begin{pmatrix}g_0\\g_1\\\vdots\\g_n\end{pmatrix}\iff\underbrace{\begin{pmatrix}\binom{0}{0}&0&\cdots&0\\-\binom{1}{0}&\binom{1}{1}&\cdots&0\\\vdots&\vdots&\ddots&\vdots\\(-1)^{n}\binom{n}{0}&(-1)^{n-1}\binom{n}{1}&\cdots&\binom{n}{n}\end{pmatrix}}_{\boldsymbol B}\begin{pmatrix}g_0\\g_1\\\vdots\\g_n\end{pmatrix}=\begin{pmatrix}f_0\\f_1\\\vdots\\f_n\end{pmatrix}\end{gather*} \)
所以上文的证明等价于证明 \(\boldsymbol A\cdot\boldsymbol B=\boldsymbol I\)

\(\boldsymbol A\)\(\boldsymbol B\) 转置之后得到的矩阵仍然是互逆的,所以我们还可以得到二项式反演的矩阵转置形式:

\[g_m=\sum_{i=m}^n{i\choose m}f_i\iff f_m=\sum_{i=m}^n(-1)^{i-m}{i\choose m}g_i \]

从组合意义来讲,这里的 \(g_m\) 是“至少”包含 \(m\) 个元素的方案数,与上文“至多”包含 \(m\) 个元素的方案的 \(g_m\) 不相类似。

在实际运用里,要灵活选用反演式子来解决问题。

应用

BZOJ 2839 集合计数

\(N\) 个元素的集合选出若干个子集使交集元素个数为 \(K\),求方案数 \(\bmod10^9+7\)

我们很难直接钦定怎么选能直接选出题目要求的若干子集。考虑更弱的性质,不妨设 \(f_k\) 表示选出若干子集,交集至少\(k\) 个元素的方案数。我们从 \(n\) 个元素中选定 \(k\) 个元素,方案数 \({n\choose k}\);因为我们不关心余下的元素是否在交集中,剩下的 \(n-k\) 个元素可以任意选/不选,与前面 \(k\) 个元素构成的子集方案数 \(2^{n-k}\);这些子集中至少选 \(1\) 个组成交集至少为 \(k\) 的若干子集的方案数就是 \(2^{2^{n-k}}-1\)。所以 \(f_k\) 就有

\[f_k={n\choose k}(2^{2^{n-k}}-1) \]

再设 \(g_k\) 表示选出若干个子集,交集恰好\(k\) 个元素的方案数,\(g_k\) 就是题目要求的答案。接下来我们可以使用 \(g_i\) 来表示出 \(f_k\):枚举 \(i\)\(k\)\(n\),实际要选定 \(k\) 个元素的方案数就是 \({i\choose k}\)。所以有 \(f_k\)\(g_i\) 的关系式

\[f_k=\sum_{i=k}^n{i\choose k}g_i \]

现在考虑二项式反演得出 \(g_k\) 关于 \(f_i\) 的式子。发现上式就是前文二项式反演的推广中那个式子,得到

\[\begin{aligned} g_k&=\sum_{i=k}^n(-1)^{i-k}{i\choose k}f_i\\ &=\sum_{i=k}^n(-1)^{i-k}{i\choose k}{n\choose i}(2^{2^{n-i}}-1) \end{aligned} \]

直接计算即可,复杂度 \(O(n\log n)\)

代码实现
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;const int maxn = 1e6 + 10, mo = 1e9 + 7;
int n, k; ll ans;
ll fac[maxn], ifac[maxn], pow2[maxn];ll qpow(ll x, ll y) {ll res = 1;while(y) {if(y & 1) (res *= x) %= mo;(x *= x) %= mo, y >>= 1;} return res;
}
void pre_calc() {fac[0] = ifac[0] = pow2[0] = 1;for(int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mo, pow2[i] = pow2[i - 1] * 2 % (mo - 1);ifac[n] = qpow(fac[n], mo - 2); for(int i = n - 1; i >= 1; i--) ifac[i] = ifac[i + 1] * (i + 1) % mo;return;
}
ll C(int x, int y) {return fac[x] * ifac[y] % mo * ifac[x - y] % mo;}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> k; pre_calc();for(int i = k; i <= n; i++) ((ans += (((i - k) & 1) ? -1 : 1) * C(i, k) * C(n, i) % mo * (qpow(2, pow2[n - i]) % mo - 1) % mo) += mo) %= mo; cout << ans;return 0;
} 

Luogu P4859 已经没有什么好害怕的了

\(n\)\(A\) 物品和 \(n\)\(B\) 物品,每个物品有一个价值。将 \(A,B\) 物品两两配对,求 \(A\) 价值大于 \(B\) 价值的组数比其余组数多恰好 \(k\) 组的方案数 \(\bmod10^9+9\)

可以先计算出 \(A\) 价值大于 \(B\) 价值的组数 \(K={n+k\over2}\),如果不是整数则不可能有符合条件的方案。

类似的,\(A\)\(B\) 大的组数恰好为 \(K\) 的限制太强,先考虑易求的弱性质。
\(A,B\) 的价值分别分别排序,可以双指针简单求出每个 \(A_i\) 有多少个 \(B\) 更小的匹配数 \(d_i\) 。考虑一个 dp:设 \(f_{i,j}\) 表示已经配对前 \(i\) 个,钦定了 \(j\) 组满足 \(A>B\) 的方案数。由于 \(A\) 有序,前面匹配上大于 \(B\) 的一定会使后面的匹配数减小。可以得到转移方程:

\[f_{i,j}=f_{i-1,j}+(d_i-(j-1))f_{i-1,j-1} \]

边界是 \(f_{0,0}=1\)
接着考虑一个 \(g_i\) 表示全部匹配上且至少有 \(i\)\(A>B\) 的方案数。如果我们可以用 \(f\) 表示出 \(g\) 就可以二项式反演求出恰好 \(K\) 组的方案数。即是在 \(f_{n,i}\) 已经钦定匹配上 \(i\) 组的基础上,剩下的 \(n-i\) 组任意匹配。就有

\[g_i=(n-j)!f_{n,i} \]

答案 \(ans_i\),即恰好匹配上 \(i\) 组的方案数可以表示出 \(g_k\)

\[g_k=\sum_{i=k}^n{i\choose k}ans_i \]

二项式反演,得到 \(ans_K\)

\[ans_K=\sum_{i=K}^n(-1)^{i-K}{i\choose K}g_i \]

直接计算即可。复杂度 \(O(n^2)\),瓶颈在 dp。

代码实现
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;const int maxn = 2e3 + 10, mo = 1e9 + 9;
int n, k, a[maxn], b[maxn], d[maxn]; 
ll fac[maxn], ifac[maxn], f[maxn][maxn], g[maxn], ans;ll qpow(ll x, ll y) {ll res = 1;while(y) {if(y & 1) (res *= x) %= mo;(x *= x) %= mo, y >>= 1;} return res;
}
void pre_calc() {ifac[0] = fac[0] = 1; for(int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mo;ifac[n] = qpow(fac[n], mo - 2); for(int i = n - 1; i >= 1; i--) ifac[i] = ifac[i + 1] * (i + 1) % mo;return;
}
ll C(int x, int y) {return fac[x] * ifac[y] % mo * ifac[x - y] % mo;}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);cin >> n >> k; pre_calc(); if((n + k) & 1) {cout << 0; return 0;} k = (n + k) / 2; for(int i = 1; i <= n; i++) cin >> a[i]; sort(a + 1, a + n + 1);for(int i = 1; i <= n; i++) cin >> b[i]; sort(b + 1, b + n + 1);for(int i = 1, j = 1; i <= n; i++) {d[i] = d[i - 1];while(a[i] > b[j] && j <= n) j++, d[i]++;}//双指针求出df[0][0] = 1;for(int i = 1; i <= n; i++) {f[i][0] = f[i - 1][0];for(int j = 1; j <= i; j++) f[i][j] = (f[i - 1][j] + (d[i] - (j - 1)) * f[i - 1][j - 1]) % mo;}//dp求f 	for(int i = 1; i <= n; i++) g[i] = fac[n - i] * f[n][i] % mo;//推式子求g for(int i = k; i <= n; i++) ((ans += (((i - k) & 1) ? -1 : 1) * C(i, k) * g[i] % mo) += mo) %= mo;//反演求anscout << ans; return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/901100.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Breach2.1

Breach2.1 信息收集 全端口扫描 ┌──(root㉿kali)-[~/vulnhub/Breach2.1] └─# nmap -sS 192.168.110.151 -p 1-65535 Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-18 09:39 CST Stats: 0:00:13 elapsed; 0 hosts completed (0 up), 1 undergoing ARP Ping Scan…

Breach2

Breach2.1 信息收集 全端口扫描 ┌──(root㉿kali)-[~/vulnhub/Breach2.1] └─# nmap -sS 192.168.110.151 -p 1-65535 Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-18 09:39 CST Stats: 0:00:13 elapsed; 0 hosts completed (0 up), 1 undergoing ARP Ping Scan…

HTML打包工具EXE工具一机一码激活码计算器小程序版

近期我们收到一些HTML一键打包EXE工具老用户反馈, 他们在使用一机一码离线功能的时候, 遇到如下的问题:外调试时突然需要激活码,手边没电脑急死人!客户现场网络受限,手机能操作就好了每次都要开电脑太麻烦,能移动端解决吗? 解决方案 针对用户集中反馈的"离线激活码…

20222227 实验一《Python程序设计》实验报告

20222227 2024-2025-2 《Python程序设计》实验一报告 课程:《Python程序设计》 班级: 2222 姓名: 赫连紫阳 学号: 20222227 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 一、实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能;(编写书中的…

【STM32】超好用的开源按键状态系统lwbtn,以及超详细的移植教程

在 button_event_handler 按钮事件处理函数中,按键按下分为:按下,释放;按下类型又分为:单击、双击、持续按下。 这每一个状态/事件,进行过后,其都会进行一次回调,即回到主函数一次,故也是分为了1、按下 2、释放 3、单击 4、双击 5、持续按下 五个回调状态。 你可以在 …

rp2040笔记[1]-使用embassy实现呼吸灯并通过命令行切换状态

使用rust的embassy在rp2040芯片核心板实现呼吸灯.摘要 使用rust的embassy在rp2040芯片核心板实现呼吸灯. 关键词 rust;embassy;rp2040;blink;pwm; 关键信息项目地址:[https://github.com/ByeIO/byeefree.rp2040_quad.embassy][package] edition = "2021" name = &quo…

014 登入页-Element-Plus的图标引入和使用

1、安装图标 2、使用 这里我们用全局注册的方法 放到这里 现在我们换一种方式 在src文件夹里面 新建文件夹global,都是一些全局的东西(这个就叫全局注册) 新建文件register-icons.ts (注册图标)这页这样写这段代码是使用 JavaScript(或可能是 TypeScript,从 app: any…

20242405 实验一《Python程序设计》实验报告

20242405 2024-2025-2 《Python程序设计》实验一报告 课程:《Python程序设计》 班级: 2424 姓名: 孙煜麟 学号:20242405 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 1.实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能 3.编写程序,练习…

day:24 python——类的三大特性

python三大特性:封装,多态,继承 一、封装: 1、封装:封装就是隐藏对象一些不希望外部所访问到的属性和方法,保证安全 2、概念:指的是在设计时,不对外提供,隐藏起来,提供公共方法以外对其方法就是把属性和方法封装到一个抽象的类中, 3、封装相当于一个黑盒子,我们将事务相…

探秘Transformer系列之(15)--- 采样和输出

从零开始解析Transformer,目标是:(1) 解析Transformer如何运作,以及为何如此运作,让新同学可以入门;(2) 力争融入一些比较新的或者有特色的论文或者理念,让老鸟也可以有所收获。探秘Transformer系列之(15)--- 采样和输出 目录探秘Transformer系列之(15)--- 采样和输出…

c语言02_数据类型上

一、c是怎么变成汇编的 1、裸函数是编译器不管的 ⑴写一个空函数(里面什么都不写),f7f5打开反汇编f11打开jmp什么都没写里面还是有一大堆(是编译器和连接器做的)⑵裸函数f7f5查看反汇编找到调用的函数0040D708f11打开再f11打开,里面一行汇编代码都没有⑶ ①空函数 f7f5运…