反演与狄利克雷卷积
修订日期:2021.12.3. 更改了很多本质上的错误。
建议首先阅读组合数学相关 Part 3. 容斥原理。
1. 本质与第一反演公式
1.1. 定义
反演是
已知
1.2. 第一反演公式
直接给出定理:如果存在
如果将
只要我们找到任何一组符合条件的
2. 二项式反演
2.1. 引入
在把玩二项式定理的时候,尝试把
嗯?和反演公式的形式长得很像!那么试试能不能用
很好,令
代数化证明:
众所周知,右边的
如果将矩阵转置,那么还可以得到另一种更常用表示,形式二:
2.2. 组合意义
二项式反演的本质是容斥原理。
注意:在定义中,
表示先钦定 个,再统计钦定情况如此的方案数,其中会包含重复的方案,因为一个方案可以有多种钦定情况。具体地,对于恰好选择 个的方案,钦定情况数为 ,故 在 中被计算了 次。切勿将 理解为普通的 的后缀和。
很多初学者最大的误区在于将
2.3. 应用
OI 界二项式反演的应用常结合动态规划:DP 求出钦定选择
2.3.1. 错排问题
个人排列,求每个人都恰好站错的方案数。即求 。
设
2.3.2. 染色问题 I(一维)
个格子, 种颜色,相邻格子颜色不同,每个颜色至少出现一次,求方案数。
设
反演得到
2.3.3. 染色问题 II(二维)
个格子, 种颜色,每行和每列至少有一个格子被涂色,求方案数。
设
对
2.3.4. 染色问题 III(三维)
个格子, 种颜色,每行和每列至少一个格子被涂色,每个颜色至少出现一次,格子可不被涂色,求方案数。
请读者结合染色问题 I. 与 II. 自行思考。设
直接计算
2.4. 参考文章
- GXZlegend —— 二项式反演及其应用(推荐)。
- liu_jiangwen ——111111 组合数学——二项式反演。
- Icyfrog —— 二项式反演入门。
- WAautomaton —— 反演定理及其应用。
2.5. 例题
*I. P6478 [NOI Online #2 提高组] 游戏
很不错的题目,可以用来入门二项式反演。设
*II. P4859 已经没有什么好害怕的了
和例题 I. 极度类似。DP 方法几乎一样,最后也都要乘以阶乘作为系数。实际上,上一题就是把本题搬到了树上。
const int mod = 1e9 + 9;
const int N = 2e3 + 5;
int n, k, ans, a[N], b[N], num[N], f[N], fc[N], ifc[N];
void add(int &x, int y) {x += y, x >= mod && (x -= mod);}
int ksm(int a, int b) {
int s = 1;
while(b) {
if(b & 1) s = 1ll * s * a % mod;
a = 1ll * a * a % mod, b >>= 1;
} return s;
}
int main(){
cin >> n >> k;
if((n & 1) != (k & 1)) return puts("0"), 0;
k = n + k >> 1, fc[0] = 1;
for(int i = 1; i <= n; i++) fc[i] = 1ll * fc[i - 1] * i % mod;
ifc[n] = ksm(fc[n], mod - 2);
for(int i = n - 1; ~i; i--) ifc[i] = 1ll * ifc[i + 1] * (i + 1) % mod;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) cin >> b[i];
sort(a + 1, a + n + 1), sort(b + 1, b + n + 1);
for(int i = 1; i <= n; i++) {
num[i] = num[i - 1];
while(num[i] < n && a[i] > b[num[i] + 1]) num[i]++;
} f[0] = 1;
for(int i = 1; i <= n; i++)
for(int j = min(num[i], i); j; j--)
add(f[j], 1ll * f[j - 1] * (num[i] - j + 1) % mod);
for(int i = k; i <= n; i++) {
int bin = 1ll * fc[i] * ifc[k] % mod * ifc[i - k] % mod;
int coef = 1ll * bin * f[i] % mod * fc[n - i] % mod;
add(ans, (i - k) & 1 ? mod - coef : coef);
} cout << ans << endl;
return 0;
}
*III. CF1228E Another Filling the Grid
设
二项式反演得到
可以
IV. [BZOJ2839]集合计数
考虑钦定
V. [BZOJ4665]小 w 的喜糖
钦定有
二项式反演即可,
*VI. [BZOJ2162]男生女生
本题可以看做割裂的两部分,一部分是求使某部点尽量大的二分图最大团,见 网络流与二分图 Part 2.5. 二分图的最大团。另一部分则需要用到二项式反演:
考虑求出男生个数
3. 单位根反演
3.1. 公式
常规证法是用等比数列求和公式,我不太喜欢,因为这样不能使初学单位根反演的同学感受到它的自然与优美。
一个比较直观的我自己的理解是
它还有另外一种形式,在做题时经常会遇到:
3.2. 应用
我们为什么要用单位根反演?钻研这个问题,不仅可以帮助我们深入理解单位根反演,在做题时我们也能更容易看出它的蛛丝马迹。
3.2.1. 将 mod 转化为求和
首先看一道经典的单位根反演入门题:给出
看到组合数和幂结合,自然想到二项式定理,可是
交换求和符号并整理,得
逆用二项式定理,得
如果你学过 NTT 就知道在模
代码见例题 I.
3.3. 例题
*I. LOJ#6485. LJJ 学二项式定理
代码。
*II. P5591 小猪佩奇学数学
推柿子:根据
略去
-
前半部分:
。根据
,得 。即
。 -
后半部分:略去负号,得
。套入单位根反演,得
。略去
,交换求和符号,得 。套入二项式定理,得
。然后发现不会了。交换求和顺序,得 。略去前面的柿子,将
记做 ,仅关注 。由于 是常数,因此将其记做 。看到这里,你发现了什么?对!是我们小学二年级就学过的错位相减法!-
如果
,求 :比对每一项的系数,有 。先特判掉
的情况,然后用等比数列求和公式得到 ,注意到 恒为 ,所以 。 -
如果
,那么原式等于 。
因此直接计算即可!时间复杂度
。 -
ll n, p, k, w, ans, sub;
int main() {
cin >> n >> p >> k, w = ksm(3, (mod - 1) / k);
ans = n * p % mod * ksm(p + 1, n - 1) % mod;
for(int x = 0; x < k; x++) {
ll om = ksm(w, x), pw = ksm(p * om + 1, n), c = inv(om);
if(c == 1) sub = (sub + pw * (k * (k - 1) / 2 % mod)) % mod;
else sub = (sub + pw * k % mod * inv(mod + c - 1)) % mod;
} cout << (ans + mod - sub * inv(k) % mod) * inv(k) % mod;
return 0;
}
4. 莫比乌斯反演
极其重要的一类反演,对于推式子的帮助很大。
4.1. 前置知识
4.1.1. 数论函数与积性函数
数论函数,就是定义域是正整数的函数。
积性函数,就是对于任意
积性函数举例(下文中会出现):
- 单位函数
。 - 常数函数
。 - 恒等函数
, 通常记作 。 - 除数函数
。 就是约数个数,记作 或 。 就是约数和,记作 。 - 大名鼎鼎的欧拉函数
。关于欧拉函数可以看 基础数论学习笔记。 - 本章节的核心莫比乌斯函数
,其中 表示 本质不同质因子个数。它的积性是显然的。
4.1.2. 狄利克雷卷积
由于学习莫比乌斯反演之前需要学会狄利克雷卷积,所以就放在这了。狄利克雷卷积竟沦落到给莫比乌斯反演作铺垫(大雾)。
定义:设
FMT 和 FWT 叫位运算卷积,FFT 和 NTT 叫加法卷积,不难发现狄利克雷卷积实际上是乘法卷积。它有如下性质:两个积性函数的狄利克雷卷积也是积性函数,积性函数的逆元也是积性函数。证明略。
4.1.3. 莫比乌斯函数
因为莫比乌斯函数是积性函数,所以它可以由线性筛求得。莫比乌斯函数有以下几条性质:
首先
该式子在推式子时比较有用。可以先证明
变式:
令上式两端同除
极其重要的一个柿子,是莫比乌斯反演的核心。将
线性筛莫比乌斯函数:根据
mu[1] = 1;
for(int i = 2; i < N; i++) {
if(!vis[i]) pr[++cnt] = i, mu[i] = -1;
for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
vis[i * pr[j]] = 1;
if(i % pr[j] == 0) break;
mu[i * pr[j]] = -mu[i];
}
}
4.1.4. 整除分块
哪里有莫反,哪里就有整除分块。详见 基础数论学习笔记 Part 5 整除分块。
4.1.5. 线性筛积性函数
见本文 Part 6.1。
4.2. 公式
若
证明:已知
证明(来自 OI - Wiki):
4.3. 直观理解
只会套皮是不行的,要想熟练运用,不仅要多刷题,也要理解莫反的本质:容斥原理。其中莫比乌斯函数就是容斥系数。也许你会问:容斥系数不应该是
不妨设接下来有
4.3.1.
当
4.3.2. 及
当
如果
结合上图,不妨将
4.3.3. 及任意自然数
有了上面的经验,你应该知道表示约数
综上,莫比乌斯函数可以看作对
4.4. 应用
一般用来化简带有
4.5. 例题
下面所有分式都是下取整。有部分整除分块的题目。
I. P2522 [HAOI2011]Problem b
二维差分将下界化为
把
莫反:
枚举约数
由于
整除分块即可,时间复杂度
II. SP5971 LCM Sum
来推式子。
单独算
至此,我们可以
这里用到了
const int N = 1e6 + 5;
ll T, pr[N], G[N], low[N], cnt;
bool vis[N];
void sieve() {
G[1] = 1;
for(int i = 2; i < N; i++) {
if(!vis[i]) pr[++cnt] = i, G[i] = 1 + 1ll * i * (i - 1), low[i] = i;
for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
vis[i * pr[j]] = 1;
if(i % pr[j] == 0) {
low[i * pr[j]] = low[i] * pr[j];
if(i == low[i]) G[i * pr[j]] = G[i] + low[i] * (pr[j] - 1) * low[i] * pr[j];
else G[i * pr[j]] = G[i / low[i]] * G[low[i] * pr[j]]; break;
} G[i * pr[j]] = G[i] * G[pr[j]], low[i * pr[j]] = pr[j];
}
}
}
int main(){
cin >> T, sieve();
while(T--) {
int n = read();
print((G[n] + 1) * n / 2), pc('\n');
} return flush(), 0;
}
*III. P4318 完全平方数
莫比乌斯函数作容斥系数的经典例子。答案满足可二分性,于是考虑如何求
const int N = 1e5 + 5;
int T, n, cnt, pr[N], vis[N], mu[N];
int main() {
cin >> T, mu[1] = 1;
for(int i = 2; i < N;i++) {
if(!vis[i]) pr[++cnt] = i, mu[i] = -1;
for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
vis[i * pr[j]] = 1;
if(i % pr[j] == 0) break;
mu[i * pr[j]] = -mu[i];
}
} while(T--){
cin >> n; ll l = n, r = n << 1;
while(l < r) {
ll m = l + r >> 1, res = 0;
for(int i = 1; i * i <= m; i++) res += mu[i] * (x / i / i);
res < n ? l = m + 1 : r = m;
} cout << l << endl;
} return 0;
}
IV. P2257 YY 的 GCD
考虑枚举
做到这里发现推不动了,因为我们不得不枚举
为什么要这样做:分母上的
*另一种推导方式:考虑对
const int N = 1e7 + 5;
int T, n, m, cnt, vis[N], pr[N >> 3], f[N], mu[N];
int main(){
for(int i = 2; i < N; i++) {
if(!vis[i]) pr[++cnt] = i, f[i] = 1, mu[i] = -1;
for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
vis[i * pr[j]] = 1;
if(i % pr[j] == 0) {f[i * pr[j]] = mu[i]; break;}
f[i * pr[j]] = -f[i] + mu[i], mu[i * pr[j]] = -mu[i];
}
} for(int i = 2; i < N; i++) f[i] += f[i - 1]; cin >> T;
while(T--) {
nll ans = 0 cin >> n >> m;
for(int l = 1, r; l <= min(n, m); l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += 1ll * (n / l) * (m / l) * (f[r] - f[l - 1]);
} cout << ans << "\n";
}
return 0;
}
V. P3455 [POI2007]ZAP-Queries
双倍经验 P2522。
VI. P2568 GCD
双倍经验 P2257.
VII. P1829 [国家集训队]Crash的数字表格 / JZPTAB
已经可以
注意到前面一坨可以整除分块,后面相当于求
const int N = 1e7 + 5;
const int mod = 20101009;
int n, m, c, pr[N >> 3], cnt, low[N];
bool vis[N]; ll f[N], ans;
ll S(ll x) {return x * (x + 1) / 2 % mod;}
int main(){
cin >> n >> m, c = min(n, m), f[1] = 1;
for(ll i = 2; i <= c; i++) {
if(!vis[i]) low[i] = pr[++cnt] = i, f[i] = (i - i * i % mod + mod) % mod;
for(int j = 1; j <= cnt && i * pr[j] <= c; j++) {
int k = i * pr[j]; vis[k] = 1;
if(i % pr[j] == 0) {
low[k] = low[i] * pr[j];
if(low[i] == i) f[k] = (k - pr[j] * pr[j] % mod * i % mod + mod) % mod;
else f[k] = f[i / low[i]] * f[low[k]] % mod; break;
} low[k] = pr[j], f[k] = f[i] * f[pr[j]] % mod;
}
}
for(int i = 2; i <= c; i++) f[i] = (f[i] + f[i - 1]) % mod;
for(int l = 1, r; l <= c; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans = (ans + S(n / l) * S(m / l) % mod * (f[r] - f[l - 1] + mod)) % mod;
} cout << ans << endl;
return 0;
}
VIII. AT5200 [AGC038C] LCMs
原题的两个枚举上界都写到
剩下来推式子的过程和上一题差不多,但是因为有
const int N = 1e6 + 5;
const int mod = 998244353;
int n, f[N], cnt, mx, pr[N >> 3];
ll b[N], inv[N], ans; bool vis[N];
int main(){
cin >> n, f[1] = inv[1] = 1;
for(int i = 1, x; i <= n; i++) cmax(mx, x = read()), ans -= x, b[x] += x;
for(int i = 2; i <= mx; i++) {
inv[i] = mod - mod / i * inv[mod % i] % mod;
if(!vis[i]) pr[++cnt] = i, f[i] = 1 - i;
for(int j = 1; j <= cnt && i * pr[j] <= mx; j++) {
int k = i * pr[j]; vis[k] = 1;
if(i % pr[j] == 0) {f[k] = f[i]; break;}
f[k] = f[i] * f[pr[j]];
}
} for(int i = 1; i <= cnt; i++) for(int j = mx / pr[i]; j; j--) b[j] += b[j * pr[i]];
for(int i = 1; i <= mx; i++) b[i] %= mod, ans += b[i] * b[i] % mod * f[i] % mod * inv[i] % mod;
cout << (ans % mod + mod) % mod * inv[2] % mod << endl;
return 0;
}
IX. P3911 最小公倍数之和
双倍经验。
X. P6156 简单题
其中
ll k;
int low[N], kp[N], F[N >> 1], G[N];
int n, cnt, ans, pr[N / 15];
bitset <N> vis;
int main(){
cin >> n >> k, k %= mod - 1, kp[1] = 1, G[1] = 1;
for(int i = 2; i <= n << 1; i++) {
if(!vis[i]) kp[i] = ksm(i, k), pr[++cnt] = low[i] = i, G[i] = i - 1;
for(int j = 1; j <= cnt && i * pr[j] <= n << 1; j++) {
int c = i * pr[j]; vis[c] = 1, kp[c] = 1ll * kp[i] * kp[pr[j]] % mod;
if(i % pr[j] == 0) {
low[c] = low[i] * pr[j];
if(i == low[i]) G[c] = i == pr[j] ? mod - pr[j] : 0;
else G[c] = 1ll * G[i / low[i]] * G[low[c]] % mod; break;
} low[c] = pr[j], G[c] = 1ll * G[i] * G[pr[j]] % mod;
}
} for(int i = 2; i <= n; i++) G[i] = (G[i - 1] + 1ll * G[i] * kp[i]) % mod;
for(int i = 1; i <= n << 1; i++) kp[i] = (kp[i - 1] + kp[i]) % mod;
for(int i = 1, j = 2; i <= n; i++, j += 2) F[i] = (F[i - 1] + 2ll * (kp[j] - kp[i] + mod) - (kp[j] - kp[j - 1]) + mod) % mod;
for(int l = 1, r; l <= n; l = r + 1) r = n / (n / l), ans = (ans + 1ll * F[n / l] * (G[r] - G[l - 1] + mod)) % mod;
cout << ans << endl;
return 0;
}
XI. P6222 「P6156 简单题」加强版
双倍经验。
5. Dirichlet 卷积
6. 高级筛法
6.1. 线性筛积性函数
前置知识:对线性筛的工作原理有一定认识。
众所周知,欧拉筛在最小质因子处筛去所有合数,但需要讨论:1. 最小质因子次数
for(int i = 2; i < N; i++) {
if(!vis[i]) pr[++cnt] = i;
for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
vis[i * pr[j]] = 1;
if(i % pr[j] == 0) break; // i * pr[j] 最小质因子 pr[j] 次数 > 1
// i * pr[j] 最小质因子 pr[j] 次数 = 1
}
}
并不是所有积性函数
for(int i = 2; i < N; i++) {
if(!vis[i]) pr[++cnt] = i, f[i] = ..., low[i] = i;
for(int j = 1; j <= cnt && i * pr[j] < N; j++) {
vis[i * pr[j]] = 1;
if(i % pr[j] == 0) { // 此时 i 与 pr 不互质, 需要另辟蹊径
low[i * pr[j]] = low[i] * pr[j];
if(i == low[i]) f[i * pr[j]] = ...; // 根据 f(p ^ k) 算 f(p ^ (k + 1))
else f[i * pr[j]] = f[i / low[i]] * f[low[i * pr[j]]]; break;
} f[i * pr[j]] = f[i] * f[pr[j]], low[i * pr[j]] = pr[j]
// i * pr 最小质因子次数 = 1, 可以通过 f[i] * f[pr] 算 f[i * pr] (积性函数的性质)
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现