狄利克雷卷积
狄利克雷卷积
随处可见的东西
建议阅读 数论函数基础 章节,了解基本概念与先要知识
全文 绝大多数 内容是对 [0] 中讲述的 粗略抄写 和 胡乱加工
1. 定义与性质
在 数论函数 上定义,两个 数论函数 \(f, g\) 的 狄利克雷卷积 为一个新的 数论函数,记作 \(f * g\)
一般的(多项式)卷积式 \(h (n) = \sum _ {x + y = n} f (x) g (y)\),可以对比一下
上式可以简记为 \(h = f * g\),直接 按照定义式计算,即 枚举因数,可以做到 \(O (n \ln n)\) 的复杂度
inline int* Dirichlet (const int *F, const int *G, const int N) {
static int H[MAXN];
for (int i = 1; i <= N; ++ i)
for (int j = i; j <= N; j += i)
H[j] += F[i] * G[j / i];
return H;
}
一些 狄利克雷卷积的性质 如下
-
交换律
\[f * g = g * f \]证明显然
-
分配律
\[(f + g) * h = f * h + g * h \]显然,\(\sum _ {d \mid n} (f + g) (d) = \sum _ {d \mid n} (f (d) + g (d))\)
-
结合律
\[(f * g) * h = f * (g * h) \]显然,均等于 \(\sum _ {xyz = n} f (x) g (y) h (z)\)
-
单位元:\(\epsilon\)
\[\epsilon * f = f \]容易发现,\(\epsilon (1) = 1, \epsilon (x) = 0 ~ (x > 1)\)
-
逆元:\(f ^ {-1}\)
\[f * f ^ {-1} = \epsilon \]满足上式的两函数 互为逆元
积性函数 一定 有且仅有一个逆元
证明
设 \(g = f ^ {-1}\),设 \(f(1) \neq 0\)(积性函数一定满足),显然有 \(g (1) f (1) = 1\)
当 \(f\) 为 积性函数 时,就有 \(g (1) = f (1) = 1\),然后尝试 递推得到 \(g\)
\[\begin {aligned} (f * g) (n) &= \epsilon (n) & n > 0 \\ \sum _ {d \mid n} { f (d) g \left ( \dfrac n d \right) } &= 0 \\ f (1) g (n) + \sum _ {d \mid n, d \neq 1} { f (d) g \left ( \dfrac n d \right) } &= 0 \\ g (n) &= \dfrac { - \sum _ {d \mid n, d \neq 1} { f (d) g \left ( \frac n d \right) } } {f (1)} \\ g (n) &= - \sum _ {d \mid n, d \neq 1} { f (d) g \left ( \dfrac n d \right) } \end {aligned} \]这同时证明了 积性函数 逆元的 存在性 和 唯一性
从 倒数第二个式子,其实我们可以推广出,\(f\) 可逆的 充要条件 实质上是 \(f (1) \neq 0\)
也就是 无需保证是积性函数
-
\(f = g\) 的 充要条件 是 \(f * h = g * h\),其中 \(h(1) \neq 0\)
两边同乘 \(h ^ {-1}\) 即可
-
积性函数 的 狄利克雷卷积 也是 积性函数
典
设存在积性函数 \(f, g\),有 \(h = f * g\),设 \(a, b\) 满足 \(a \perp b\)
\[\begin {aligned} h (ab) &= f (ab) * g (ab) \\ &= \sum _ {d \mid a, t \mid b} { f (dt) g \left ( \dfrac {ab} {dt} \right ) } \\ &= \sum _ {d \mid a} { \sum _ {t \mid b} { f (d) f (t) g \left ( \dfrac a d \right ) g \left ( \dfrac b t \right ) } } \\ &= \left ( \sum _ {d \mid a} { f (d) g \left ( \dfrac a d \right ) } \right ) \left ( \sum _ {t \mid b} { f (t) g \left ( \dfrac b t \right ) } \right ) \\ &= h (a) h (b) \end {aligned} \]即得证
-
积性函数 的 逆元 也是 积性函数
设存在 积性函数 \(f\),其 逆元 \(f ^ {-1}\),下文保证 \(ab \neq 0\),即 \(\epsilon (a) \epsilon (b) = 0\)
\[\begin {aligned} f ^ {-1} (ab) &= - \sum _ {d \mid ab, d \neq 1} { f (d) f ^ {-1} \left ( \dfrac {ab} d \right) } \\ &= - \sum _ {i \mid a, j \mid b, ij \neq 1} { f (i) f (j) f ^ {-1} \left ( \dfrac a i \right ) f ^ {-1} \left ( \dfrac b j \right ) } \\ &= - \sum _ {i \mid a, j \mid b} { f (i) f (j) f ^ {-1} \left ( \dfrac a i \right ) f ^ {-1} \left ( \dfrac b j \right ) } - (-f (1) f (1) f ^ {-1} (a) f ^ {-1} (b)) \\ &= f ^ {-1} (a) f ^ {-1} (b) - \sum _ {i \mid a} { f (i) g \left ( \dfrac a i \right ) } \sum _ {j \mid b} { f (j) g \left ( \dfrac b j \right ) } \\ &= f ^ {-1} (a) f ^ {-1} (b) - \epsilon (a) \epsilon (b) \\ &= f ^ {-1} (a) f ^ {-1} (b) \end {aligned} \]综合这两个性质,我们发现 两个积性函数 的 积 和 商 都是 积性函数,但 和差 不是
2. 线性狄利克雷卷积
\(O (n \ln n)\) 还是 太菜了,现在我们来 加速它,考虑 线性做法
如果 \(f, g\) 是 积性函数,我们可以尝试 利用性质 进行一些优化
第一部分 是 简单的,而 第三部分 可以通过 线性筛 处理,\(p ^ k\) 就是 \(n\) 最小质因子的最高次幂
于是难点在于 快速求出第二部分,即 任意质数幂项 的 值
显然,若 \(f, g\) 在 质数幂 处的取值 已经求出,我们需要 \(O (k)\) 的时间来计算 \(h\) 的值
\(f, g\) 在 质数幂 处的取值也可以 线性筛筛出,或可能 \(O(1)\) 求得(否则就不好做了)
考虑估算 此时的复杂度,显然,每个 $ \le \sqrt [k] n$ 的 质数 \(p\) 会贡献 \(O(k)\) 的复杂度
首先,显然 \(\ln n\) 与 \(\sum _ {x = 1} ^ {\log _ 2 n} 1 = \log _ 2 n\) 同阶
故若 \(k ^ 2 \sqrt [k] n\) 的上界 与 \(k\) 无关,则 \(T (n) = k ^ 2 \sqrt [k] n\)
显然,随着 \(k\) 增长,\(k ^ 2\) 单调递增,\(\sqrt [k] n\) 单调递减,并显然 \(k ^ 2\) 增长速度远小于 \(\sqrt [k] n\) 减小速度
故我们认为,其极大值必在定义域端点处取到,验证 \(k = 1, k = \log _ 2 n\) 容易证明其小于 \(n\),故
故得证,用 线性筛 求 两个质数幂处已知的积性函数 的 狄利克雷卷积 可以做到 \(O(n)\) 复杂度
这也就是 线性筛 处提到的 \(O(k)\) 求出 质数幂 处值的方法
Luogu P6222 「P6156 简单题」加强版
推狮子,需要用一些据说是 经典套路 的东西,即 枚举 \(\gcd\),然后需要用 莫比乌斯反演*
* : 这个东西后面会 细讲,这里只先 放个式子,可以使用 [3] [4] 这些资料来深入学习
\[\begin {aligned} \sum _ {d \mid n} \mu (d) = [n = 1] \end {aligned} \]还有一个更泛化的
\[\begin {aligned} f (n) = \sum _ {d \mid n} g (d) \Longrightarrow g (n) = \sum _ {d \mid n} \mu (d) f (\dfrac n d) \end {aligned} \]
然后启动!(后面有设 \(T = td\))
然后死了,考虑分成两部分
\(f\) 函数显然是 积性函数,可以 线性筛 狄利克雷卷积 直接 预处理掉
对于 \(g\) 函数,我们继续往下推,考虑枚举 \(s = i + j\) 来 省去一个参数
对于不好处理的 \(\max\), \(\min\),我们直接 钦定结果,即讨论 \(n > s - 1\), \(n < s - 1\) 两种情况贡献
丢到线性筛里一起处理就行了,$s ^ k $ 显然是 完全积性函数
#include <bits/stdc++.h>
import std;
const int MAXN = 100005;
using namespace std;
uint32_t T, N, K, Q;
uint32_t Cnt = 0;
uint32_t S1[MAXN], S2[MAXN], F[MAXN], P[MAXN];
bool Vis[MAXN];
inline uint32_t Qpow (uint32_t a, uint32_t b) {
uint32_t Ret = 1;
while (b) {
if (b & 1) Ret = Ret * a;
a = a * a, b >>= 1;
}
return Ret;
}
inline void Sieve () {
uint32_t tmp1, tmp2;
S1[1] = F[1] = 1;
for (uint32_t i = 2; i <= N; ++ i) {
if (!Vis[i]) {
P[++ Cnt] = i, S1[i] = Qpow (i, K);
F[i] = i - 1;
}
for (uint32_t j = 1; P[j] * i <= N && j <= Cnt; ++ j) {
Vis[tmp1 = P[j] * i] = 1;
S1[tmp1] = S1[P[j]] * S1[i];
if (i % P[j] == 0) {
tmp2 = i / P[j]; // ATTENTION tmp2 = tmp1 / (P[j] ^ 2)
if (tmp2 % P[j] == 0) F[tmp1] = 0;
if (tmp2 % P[j] != 0) F[tmp1] = - P[j] * F[tmp2];
break ;
}
F[tmp1] = F[P[j]] * F[i];
}
}
for (uint32_t i = 1; i <= N; ++ i) F[i] = F[i - 1] + F[i] * S1[i];
for (uint32_t i = 1; i <= N; ++ i) S2[i] = S2[i - 1] + S1[i] * i, S1[i] += S1[i - 1];
}
inline uint32_t G (const int x) {
return (2 * x + 1) * (S1[x << 1] - S1[x]) - (S2[x << 1] - S2[x]) + (S2[x] - S2[1]) - (S1[x] - S1[1]);
}
inline void Solve () {
uint32_t Ans = 0, L, R;
cin >> Q;
for (L = 1; L <= Q; L = R + 1)
R = Q / (Q / L), Ans += G (Q / L) * (F[R] - F[L - 1]);
cout << Ans << '\n';
}
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T >> N >> K;
N <<= 1, Sieve ();
while (T --) Solve ();
return 0;
}
3. 狄利克雷前缀和
对于 任意数论函数\(f\),其与 常数函数 \(1\) 做 狄利克雷卷积,等价于对 \(f\) 做 狄利克雷前缀和
即 \(g = f * 1 = \sum _ {d \mid n} f (d)\)
也就是计算 给定函数 在其 所有因数处 的取值和,这东西常常有 良好的性质
暴力做是 简单的,枚举每个数倍数即可,时间复杂度 \(O (n \ln n)\),其实并不慢
但是我们还有 更加快 并且 也比较简单的做法 值得学习
考虑有 \(n = \prod p_i ^ {c_i}, d = \prod p_i ^ {k _i}\),那么 \(d \mid n\) 等价于 \(\forall i, k_i \le c_i\)
这里我们设 乘式均有无穷项,即 \(c_i, k_i\) 可以为 \(0\),两边 \(p_i\) 等价
于是我们相当于对于这个 无穷项数列 \(c_i\) 做了类似 枚举子集 的操作,像 高维前缀和 的形式
于是根据 高维前缀和 的实现,我们考虑 枚举每一维 并关于该维做前缀和
同时由于 总状态数有限,我们又可以把这些东西压到一个 一维数组中转移
具体而言,我们枚举质数 \(p_i\),枚举其可能的倍数 \(k\)(使得 \(p_i k \le n\))
设答案函数 \(g (n)\),则我们每次将 \(g (p_ik)\) 加上 \(g (k)\) 这个贡献
理解一下,\(p_ik\) 唯一分解 后只比 \(k\) 多了一次 \(p_i\),即对应项 \(c_i\) 加上了 \(1\)
于是上述转移用高维前缀和的思路,就是把 每个质数看作一维,每次对这一维做前缀和
根据前面 埃式筛法 的结论,容易知道这样时间复杂度是 \(O (n \ln \ln n)\) 的,写法和埃式筛很像
Luogu P5495 【模板】Dirichlet 前缀和
#include <bits/stdc++.h>
const int MAXN = 20000005;
using namespace std;
uint32_t N, Seed, Cnt, Ans;
uint32_t P[MAXN >> 3], A[MAXN];
bool Vis[MAXN];
inline void Next () {
Seed ^= Seed << 13, Seed ^= Seed >> 17, Seed ^= Seed << 5;
}
inline void Prime () {
for (uint32_t i = 2; i <= N; ++ i) {
if (!Vis[i]) P[++ Cnt] = i;
for (uint32_t j = 1; j <= Cnt && P[j] * i <= N; ++ j) {
Vis[i * P[j]] = 1;
if (i % P[j] == 0) break ;
}
}
}
inline void Dirichlet () {
for (uint32_t i = 1; i <= Cnt; ++ i)
for (uint32_t j = 1; P[i] * j <= N; ++ j)
A[P[i] * j] += A[j];
}
int main () {
cin >> N >> Seed, Next ();
for (uint32_t i = 1; i <= N; ++ i) A[i] = Seed, Next ();
Prime (), Dirichlet ();
for (uint32_t i = 1; i <= N; ++ i) Ans ^= A[i];
cout << Ans << '\n';
return 0;
}