狄利克雷卷积(Dirichlet)& 杜教筛
狄利克雷卷积(Dirichlet)
基础知识
定义
数论函数 \(f(x)\) 和 \(g(x)\) 的 Dirichlet 卷积,记作 \(f*g\),定义为数论函数:
以下给出一些基本函数:
- 幂函数 \(id_t\):\(id_t(n)=n^t\)。
- 元函数 \(\varepsilon\):\(\varepsilon(n)=[n=1]\)。
- 恒等函数 \(1\):\(1(n)=1\),也就是 \(id_0\)。
- \(id\):\(id(n)=n\),也就是 \(id_1\)。
- 莫比乌斯函数 \(\mu\)。
- 欧拉函数 \(\varphi\)。
性质
Dirichlet 卷积的代数性质非常优秀:
-
交换律:\(f*g=g*f\)。
证明:由于一个数 \(n\) 的因数总是成对出现,根据定义式,得证。
-
结合律:\((f*g)*h=f*(g*h)\)。
证明:展开左式得:
\[((f*g)*h)(n)=\sum_{xyz=n}f(x)g(y)h(z)=(f*(g*h))(n) \]得证。
-
分配率:\((f+g)*h=f*h+g*h\)。
证明:展开左式得:
\[((f+g)*h)(n)=\sum_{xy=n}(f(x)+g(x))h(y)=\sum_{xy=n}f(x)h(y)+\sum_{xy=n}g(x)h(y)=f*h+g*h \] -
单位元:\(f*\varepsilon=\varepsilon*f=f\),其中 \(\varepsilon(n)=[n=1]\) 为卷积的单位元。
常见公式
利用常见的公式,可以让原函数变形,从而求解复杂问题:
-
莫比乌斯函数的核心性质 \(\mu*1=\varepsilon\)。
证明:本质含义: \(n\) 的所有约数的莫比乌斯函数和,只有当 \(n=1\) 时为 \(1\),其余为 \(0\)。证明详见莫比乌斯函数。
-
欧拉函数的核心性质 \(\varphi*1=id\)。
证明:这个也算老熟人了,本质为:\(\sum\limits_{d|n}\varphi(d)=n\)。详见欧拉函数。
-
另外,还有带权的基本函数,如 \((id\cdot \mu)*id=\varepsilon\),也就是 \(f(n)=n\cdot \mu(u)\):
证明:将左式展开:
\[\sum_{d|n}(id\cdot \mu)(d)\cdot\cfrac{n}{d}=\sum_{d|n}d\cdot \mu(d)\cdot(\cfrac{n}{d}) \]合并相消:
\[n\sum_{d|n}\mu(d)=n\cdot\varepsilon(n)=\varepsilon(n) \] -
带权欧拉函数同理: \((id\cdot \varphi)*id=id_2\):
杜教筛
前置知识:数论分块(似乎卷积几乎都会使用)。
杜教筛是用与处理积性函数的前缀和的方法,它可以在低于线性时间复杂度内处理 \(S_f(n)=\sum\limits^{n}_{i=1}f(i)\)。
公式推导
实际上,杜教筛的本质是通过构造函数 \(g\),\(h\),使得 \(f*g=h\) 成立,再依据 Dirichlet 卷积变形求解前缀和。
现在进行推导:
对两侧同求前缀和:
按 Dirichlet 卷积展开:
交换枚举顺序(数论常用变形方式):
可以发现,\(\sum\limits_{i=1}^{\lfloor \frac{n}{d}\rfloor}f(i)\) 可以表示 \(f\) 的前缀和,以下记作 \(S_f(\lfloor \cfrac{n}{d}\rfloor)\)。
我们想要求的是 \(S_f(n)\),注意到当 \(d=1\)时,\(\lfloor \cfrac{n}{d}\rfloor=1\),将这一项提出来:
移相得:
我们成功将问题规模缩小了。观察右式,\(h\) 与 \(g\) 一般选择能够快速求解的函数,而 \(S_f(\lfloor \cfrac{n}{d}\rfloor)\) 则可通过数论分块和递归为子问题的方式求解。
构造技巧
对于未知性质的积性函数 \(f\),我们无法直接想出 \(g\) 和 \(h\),但能够通过一些方法凑出来。
根据 Dirichlet 卷积的定义可知:、
又因为积性函数通用性质 \(f(1)=g(1)=1\)。
所以:
即:
我们只需要将 \(f\) 拆分成 \(h - g\) 的形式即可。(注意 \(h\) 和 \(g\) 一般为数论的基本函数)
例:当 \(f(n)=n\cdot\varphi(n)\),时,构造 \(g\) 和 \(h\) 使得 \(f*g=h\)。
- 代入质数 \(p\),得:\(f(p)=p\cdot\varphi(p)=p(p-1)=p^2-p\)。
- 显然可得 \(g=p=id\),\(h=p^2=id_2\)。
注意有些题目会挖坑,需代入 \(p\) 和 \(p^2\) 验证。
例题
莫比乌斯函数前缀和
根据 \(\mu*1=\varepsilon\),构造出 \(f * g=h\) 的形式,套用公式:
欧拉函数前缀和
根据 \(\varphi*1=id\),构造出 \(f * g=h\) 的形式,套用公式:
代码(洛谷杜教筛模板):
注意:需使用记忆化搜索。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e7;
// 线性筛
int prime[maxn + 5],book[maxn + 5],pcnt = 0;
ll mu[maxn + 5],phi[maxn + 5];
ll Smu[maxn + 5],Sphi[maxn + 5];
void init() {
for (int i = 1;i <= maxn;i ++) book[i] = 1;
book[0] = book[1] = 0;
mu[1] = phi[1] = 1ll;
for (int i = 2;i <= maxn;i ++) {
if (book[i] == 1) {
prime[++ pcnt] = i;
mu[i] = -1,phi[i] = i - 1;
}
for (int j = 1;j <= pcnt && i * prime[j] <= maxn;j ++) {
book[i * prime[j]] = 0;
if (i % prime[j] == 0) { // p 为 i的最小质因子
mu[i * prime[j]] = 0;
phi[i * prime[j]] = phi[i] * prime[j];
break;
} else {
mu[i * prime[j]] = -mu[i];
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
Smu[0] = Sphi[0] = 0;
for (int i = 1;i <= maxn;i ++) {
Smu[i] = Smu[i - 1] + mu[i];
Sphi[i] = Sphi[i - 1] + phi[i];
}
}
unordered_map<ll,ll> bookmu,bookphi;
ll get_Smu(ll n) {
if (n <= maxn) return Smu[n];
if (bookmu.count(n)) return bookmu[n];
// 数论分块
ll sum = 0ll;
for (ll l = 2,r = 0;l <= n;l = r + 1) {
r = (n / (n / l));
sum += 1ll * get_Smu(n / l) * (r - l + 1);
}
return bookmu[n] = 1ll - sum;
}
ll get_Sphi(ll n) {
if (n <= maxn) return Sphi[n];
if (bookphi.count(n)) return bookphi[n];
// 数论分块
ll sum = 0ll;
for (ll l = 2,r = 0;l <= n;l = r + 1) {
r = (n / (n / l));
sum += 1ll * get_Sphi(n / l) * (r - l + 1);
}
return bookphi[n] = (1ll * n * (n + 1) / 2) - sum;
}
int T;
ll n;
int main() {
ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);
init();
cin >> T;
while (T --) {
cin >> n;
cout << get_Sphi(n) << " " << get_Smu(n) << "\n";
}
return 0;
}
洛谷P3768 简单的数学题
输入一个整数 \(n\) 和一个整数 \(p\),你需要求出:
\[\left(\sum_{i=1}^n\sum_{j=1}^n ij \gcd(i,j)\right) \bmod p \]
现对原式进行化简:
先转化成常见形式(化为 \([gcd(i,j)=1]\) 即 \(\mu\) 的形式):
交换枚举顺序:
显然后面是常数(注:\(S_f\) 表示 \(f\) 的前缀和):
最巧妙的一步来了,令 \(T = dt\),易知 \(1\le T\le n\),在外层枚举 \(T\):
化简消元:
可以发现是一个 \(f(t)\cdot g(\cfrac{C}{t})\) 的形式,考虑将右侧转化为 Dirichlet 卷积:
即:
通过常见公式,我们知道 \(\varphi*1=id\),两侧同乘 \(\mu\),可得:
代入原式就转化为了:
只要能够求得 \(S_f(n)=\sum\limits_{T=1}^nT^2\cdot\varphi(T)\) 就能够利用数论分块解决了。
现在使用杜教筛,但如何构造 \(g\) 和 \(h\)。
依据前面的方法,代入质数 \(p\),则有:
由 \(f+g=h\) 得:
易得:\(g=p^2=id_2,h=p^3=id_3\)。
套用杜教筛公式:
代码:注意 maxn 的选择。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 5e6; // 注意 1e6:TLE 1e7:MLE
ll mod = 0;
ll inv(ll x) {
ll res = 1ll,p = mod - 2ll;
while (p) {
if (p & 1) res = res * x % mod;
p >>= 1,x = x * x % mod;
}
return res;
}
ll inv2 = 0,inv6 = 0;
int book[maxn + 5],prime[maxn + 5],pcnt = 0;
ll phi[maxn + 5];
ll sum[maxn + 5];
void init() {
for (int i = 1;i <= maxn;i ++) book[i] = 1;
phi[1] = 1,book[0] = book[1] = 0;
for (int i = 2;i <= maxn;i ++) {
if (book[i]) {
prime[++ pcnt] = i;
phi[i] = i - 1;
}
for (int j = 1;j <= pcnt && i * prime[j] <= maxn;j ++) {
int p = prime[j];
book[i * p] = 0;
if (i % p == 0) {
phi[i * p] = phi[i] * p;
break;
} else phi[i * p] = phi[i] * (p - 1);
}
}
sum[0] = 0ll;
for (ll i = 1;i <= (ll)maxn;i ++) {
sum[i] = sum[i - 1] + (i * i % mod) * phi[i] % mod;
sum[i] %= mod;
}
}
ll Sid2(ll n) {
n %= mod;
return n * (n + 1) % mod * (2 * n + 1) % mod * inv6 % mod;
}
ll Sid3(ll n) {
n %= mod;
ll res = (n * (n + 1) % mod) * inv2 % mod;
return res * res % mod;
}
unordered_map<ll,ll> mp;
ll get_sum(ll n) {
if (n <= maxn) return sum[n];
if (mp.count(n)) return mp[n];
ll res = 0ll;
for (ll l = 2ll,r;l <= n;l = r + 1ll) {
r = (n / (n / l));
ll t = (Sid2(r) - Sid2(l - 1) + mod) % mod;
res += (get_sum(n / l) * t) % mod;
res %= mod;
}
res = (Sid3(n) - res + mod) % mod;
return mp[n] = res % mod;
}
int main() {
ll n; cin >> mod >> n;
init(); inv2 = inv(2),inv6 = inv(6);
ll ans = 0;
for (ll l = 1ll,r;l <= n;l = r + 1ll) {
r = (n / (n / l));
ans += Sid3(n / l) * ((get_sum(r) - get_sum(l - 1) + mod) % mod) % mod;
ans %= mod;
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号