数论函数小记
摘自 command_block 大佬的笔记
基础:
-
积性函数:若当 \((i,j) = 1\) 有 \(f(i \times j) = f(i) \times f(j)\),则称 \(f\) 为积性函数。
-
完全积性函数:若当 \(f(i \times j) = f(i) \times f(j)\) , 则称 \(f\) 为完全积性函数。
-
\(I(n)\) : 值恒为 1 的函数。
-
\(e(n)\) : 当 \(n = 1\) 时值为 1,否则为 0 。
-
\(id(n)\) : \(id(n) = n\)。
-
tips:这些都为完全积性函数。
狄利克雷卷积:
给定两个数论函数 \(f, g\) ,则这两个函数的狄利克雷卷积 \((f * g)\) 定义为:
特殊的, 我们一个函数 \(f\) 的逆 \(f^{-1}\) 定义为: \(f * f^{-1} = e\)
莫比乌斯函数:
-
引理 1:
若两个函数 \(f,g\) 的卷积为积性函数,且 \(g\) 为积性函数,则 \(f\) 也是积性函数。
我们定义 \(\mu\) 为一个逆为 \(I\) 的积性函数,既满足 \(\mu * I = e\),接下来我们推一下它的定义。
显然 \(\mu(1) = 1\)。
- tips: 观察一个积性函数,可以先从 \(p^k\) 的情形入手。
\(\mu(p^k):\) 当 \(k = 0\) 时,函数值为 1。
由卷积的定义得: \(\mu(p^k) = \mu(p^k) + \mu(p^{k-1}) + ... + \mu(p^0)\)
即为: \(\mu(p^k) + \mu(p^{k-1}) + ... + \mu(p^0) = 0\)
又因为 \(\mu\) 为积性函数,所以 \(\mu(n) = \prod _{i = 1}^{cntp} \mu(p_i^{c_i})\)
则可以得到 \(\mu\) 的定义了:
当 \(n\) 有一个次数大于一的质因子时,\(\mu(n) = 0\)
否则 \(\mu(n) = (-1)^ {cntp}\).
一些常见的结论:
-
卷积式:
\(\mu * I = e\)
$ I * \varphi = id$ 即 \(id * \mu = \varphi\)
\(d = I * I\) (\(d\) 是一个数的约数个数)
\(F = f * I\) 则 \(f = \mu * F\) (重要!!)
-
函数运算:
\(\mu(ij) = \mu(i)\mu(j)[i⊥j]\)
\(d(ij) = \sum _{x | i}\sum_{y | j} [x⊥y]\)
\(\sum ^ n _ {i = 1} d(i) = \sum ^ n _{i = 1} \lfloor \frac{n}{i} \rfloor\)
莫比乌斯反演:
十分灵活,主要基于上面的柿子和一些奇怪结论。
看一道例题 P2257 YY的GCD:
题目就是让你求:
(看起来很废话,但是也是重要的)
令 \(i, j\) 都除掉 \(p\):
代入 \(\mu * I = e\):
把 \(\mu\) 提到前面:
整理一下:
- tips: 遇到乘积式需要考虑代换掉并变为整除形式。
还是不好做,发现 \(dp\) 为乘积式很不好,令 \(k = dp\)。
- tips: 发现求和式性质不明显时,可以考虑交换枚举变量或交换求和式顺序。
注意到这个柿子只有 \(\mu\) 项与 \(p\) 相关,于是交换求和式顺序。
令 \(f(x)\) 为后面那一坨柿子,用埃氏筛即可预处理出来。
接着使用整除分块技巧计算前面的柿子即可。
code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e7 + 10;
int prime[N], tot, mu[N], g[N];
bool isprime[N];
void init(){
for(int i = 2; i < N; i++) isprime[i] = true;
isprime[1] = false, mu[1] = 1;
for(int i = 2; i < N; i++){
if(isprime[i]) prime[++tot] = i, mu[i] = -1;
for(int j = 1; j <= tot && i * prime[j] < N; j++){
isprime[i * prime[j]] = false;
if(i % prime[j] == 0) break;
mu[i * prime[j]] = -mu[i];
}
}
for(int i = 1; i <= tot; i++){
for(int j = prime[i]; j < N; j += prime[i]){
g[j] += mu[j / prime[i]];
}
}
for(int i = 2; i < N; i++) g[i] += g[i - 1];
}
int H(int n, int m){
int ret = 0, l = 1, r, siz = min(n, m);
while(l <= siz){
r = min(siz, min(n / (n / l), m / (m / l)));
ret += (g[r] - g[l - 1]) * (n / l) * (m / l);
l = r + 1;
}
return ret;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
init(); int T; cin >> T;
while(T--){
int n, m;
cin >> n >> m;
cout << H(n, m) << "\n";
}
return 0;
}
还有另一种反演形式:
\(\sum^{n}_{i = 1} \sum^{m}_{j=1}f(\gcd(i,j))\)
向上面一样的推法可以得出这个柿子等于:
\(\sum^{n}_{x=1} \lfloor \frac{n}{x}\rfloor\lfloor \frac{m}{x}\rfloor\sum_{d|x} \mu(d)f(\frac{x}{d})\)
若 \(f\) 是积性函数,则可以欧拉筛,否则埃氏筛即可。
杜教筛:
杜教筛是一种高效计算一类数论函数前缀和的筛法。
杜教筛的核心是构造两个合适的数论函数,使得 \(h = f * g\) (\(f\) 为待求函数),而且 \(h, g\) 的前缀和易求。
杜教筛公式:
我们令 \(S(n) = \sum_{i = 1} ^ n f(i)\)。
于是有:
做一个小小的变换得到:
进行化简:
整理后有:
将 \(S(n)\) 拆出来:
这就是杜教筛公式了。
实现:
首先我们算出 \(\sqrt n\) 内 \(f\) 的前缀和。
对于一个 \(S(n)\),我们对它做整除分块并递归计算 \(S(\lfloor \frac{n}{l} \rfloor)\)。
可以证明时间复杂度为 \(O(n ^{\frac{2}{3}})\)(然而我太菜了并不会呜呜呜)。