流 泪 道 别 并 不 适 合 我 们 |

Ydoc770

园龄:6个月粉丝:6关注:13

2025-02-08 19:26阅读: 20评论: 0推荐: 0

从零开始学杜教筛

实际上略过了一部分基础数论。
杜教筛时间复杂度的证明还需要一点微积分基础。

积性函数

数论函数是定义域在整数上的函数,其中积性函数因为其具有十分朴实且优美的性质,成为了 OI 数论中不可或缺的基础。

定义

设数论函数 f(n),若对于任意 ab 满足 f(a)f(b)=f(ab) 则称 f(n) 为积性函数。
特别的,若对于任意 a,b 满足 f(a)f(b)=f(ab),则称 f(n) 为完全积性函数。

常见的积性函数有:

  • 单位函数:ε(n)=[n=1](完全积性)
  • 恒等函数:idk(n)=nk(完全积性),当 k=1 简记为 id(n)=n
  • 常数函数:1(n)=1(完全积性)
  • 除数函数:σk(n)=d|ndkσ0(n) 即因数个数,简记为 d(n)σ1(n) 即因子之和,简记为 σ(n)
  • 欧拉函数:φ(n)=d=1n[gcd(d,n)=1],即 n 以内与 n 互质的数的个数。
  • 莫比乌斯函数: μ(x)={1(x=1)(1)k(xxk)0(x)

同时有两个重要的推论:

  1. 积性函数 f 一定满足 f(1)=1,因为 f(1)=f(1×1)=f(1)×f(1)
  2. 通过所有质数处的点值可以唯一确定完全积性函数;通过全部 pk 处点值可以唯一确定积性函数,由于唯一分解定理。

狄利克雷卷积

定义

对于两个数论函数 f,g,他们的狄利克雷卷积定义为

(fg(n))=d|nf(d)g(nd)

性质

狄利克雷卷积有很多优良的性质:

  • 交换律:fg=gf
  • 结合律:fgh=f(gh)
  • 单位元:取 ε(n)=[n=1],则对任意数论函数
    f 都有 fε=εf=f
  • 两个积性函数的狄利克雷卷积仍然是积性函数。

狄利克雷逆

已知数论函数 f,求一个数论函数 g 使 fg=ε

应用

这个东西有啥用呢?我们不妨试着找找 1(n)=1 的狄利克雷逆。
μ=11,我们简单计算一下前几项:

n 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
μ(n) 1 1 1 0 1 1 1 0 0  1 1  0 1  1  1  0

p,q 为互异质数,我们可以简单观察到一些规律:

  • n=pμ(p)=1
  • n=pqμ(pq)=1
  • n=pk (k>1)μ(pk)=0

这些条件看上去都是必要的,但是并没有充分性。
根据狄利克雷卷积的性质,由于 1ε 都是积性函数,所以 μ 也是。并且由积性函数推论 2,这些 pk 处的点值已经唯一确定了一个积性函数。归纳得出:

μ(x)={1(x=1)(1)k(xxk)0(x)

事实上,1(n) 的狄利克雷逆就是 μ(n) 的定义。

我们考虑另一个狄利克雷卷积:μid,其中 id(n)=n
φ=μid,我们简单计算一下前几项:

n 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
φ(n) 1 1 2 2 4 2 6 4 6  4 10  4 12  6  8  8

p 为质数,我们可以简单观察到一些规律:

  • n=pμ(p)=p1
  • n=pk (k>1)μ(pk)=(p1)pk1

归纳得出:

φ(n)=ni=1kpi1pi, where n=i=1kpiri

事实上,(μid)(n) 就是 φ(n) 的定义。

恒等式

我们有以下狄利克雷卷积恒等式:

  • μ1=ε
  • μid=φ
  • φ1=id
  • 11=d=σ0
  • 1idk=σk

其中前三个恒等式非常重要,在杜教筛式子的推导中也会出现。

数论分块(整除分块)

这是几乎所有数论题都会用到的计算方法,非常重要。一切来源于一个精巧的观察。

重要观察

对一个正整数 n,所有 ni 的取值有 O(n) 种。

证明:ix 时,一一对应 O(n) 种不同的取值;当 i>n 时,由于 nin,所以此时 ni 也只有 O(n) 种取值。

枚举取值

现在我们有一个 l 表示一段取值相同的数的左端点,要求这段数的右端点 r。即已知 nl=nr,求 rmax

k=nl=nr,有 knr<k+1。同时取倒数,得到 1k+1<rn1k。求 r 上界拿出右边不等式,化成 rnk。由于求的 r 是一个整数,不妨加个下取整得到 rnk=nnl。即 rmax=nnl

我们可以不断地求出 r=nnl,统计整段贡献,再令 l=r+1。这样我们就做到了 O(n) 快速求值。

线性筛

原理

顾名思义,线性筛可以在严格线性时间复杂度筛质数或各种积性函数。其原理是用每个数的最小质因子来筛去它。

正确性

具体实现是维护从小到大的质数 pj,每次筛去当前数 i×pj,此时默认了 pji×pj 的最小质因子。当枚举到 pj 整除 i 时,我们发现:如果之前没有枚举到 i 的其他质因子,那么 pji 的最小质因子;在这之后,如果有另一个更大的 pj 整除 i,那么 i×pj 的最小质因子仍然是 pj 而不是 pj,我们不能用 pj 来筛 i×pj,这样不符合原理。所以在第一处 pj 整除 i 时就需要终止对于 i×pj 的筛。而在此之前筛去的 i×pj 如果有更小的质因子,那一定是 i 更小的质因子,在之前就会终止。所以我们的默认也是正确的。

复杂度

复杂度如何保证?对于每个数去筛都有一次终止,即 O(n) 次终止;在终止之前,每一个数 i×pj 都只会被最小质因子筛一次,同时这个 i×pj 只会被最小质因子打上一次非质数的标记。这几个行为都是 O(n) 的,故总复杂度也是 O(n)

代码实现

贴一份线性筛代码,其实很简短。

线性筛 μφ
void init() {
mu[1] = 1, phi[1] = 1;
for(int i = 2; i <= maxv; i++) {
if(isp[i]) {
primes.push_back(i);
mu[i] = -1, phi[i] = i - 1;
}
for(int p : primes) {
if(i * p > maxv) break;
isp[i * p] = false;
if(i % p) {
mu[i * p] = -mu[i], phi[i * p] = phi[i] * (p - 1);
}
else {
mu[i * p] = 0, phi[i * p] = phi[i] * p;
break;
}
}
}
return;
}

杜教筛

简介

对于数论函数 f,杜教筛可以在低于线性时间复杂度(O(n34) 甚至 O(n23))计算 S(n)=i=1nf(i) 的点值。

式子推导

找另一个恰当的数论函数 g(通常是积性函数),考虑 fg

i=1n(fg)(i)=i=1nd|if(d)g(id)

为了避免枚举因数,我们更换指标:用新的 d 代替 id,新的 i 代替 d。由于 di 的因数,如果枚举 d,所有合法的 i 表示的是 dn 以内合法的倍数,共有 nd 个。于是:

i=1nd|if(d)g(id)=d=1ng(d)i=1ndf(i)

把对 f(i) 求和换成 S(nd),再把 d 换成更简单的指标:

i=1ng(i)S(ni)

(以上部分也可以用于在其他时候处理狄利克雷卷积)

接下来我们处理 S(n)。如果你选取的 g 是积性函数,那么 S(n)=g(1)S(n) 可以写成 i=1ng(i)S(ni)i=2ng(i)S(ni)(如果不是,则整体除以 g(1))。即有:

i=1n(fg)(i)i=2ng(i)S(ni)

fg=h,则:

g(1)S(n)=i=1nh(i)i=2ng(i)S(ni)

如果 hg 的前缀和好求,那么我们只需要数论分块就可以快速求出 S(ni)。所以一个恰当的 g 条件是 fgg 的前缀和都好求。

时间复杂度

R(n)={nk:k=2,3,,n}

我们认为求 hg 的前缀和是 O(1) 的。每个 S(k) (kR(n)) 均只会计算一次,其中 |R(n)|=O(n)(数论分块结论)。

设计算一次 S(n) 的时间复杂度为 T(n),则:

T(n)=kR(n)T(k)=Θ(n)+k=1nO(k)+k=2nO(nk)=O(0n(x+nx)dx)=O(n34).

若我们可以预处理一部分 S(k),其中 k=1,2,,mmn。设预处理时间复杂度为 T0(m),此时 T(n) 为:

T(n)=T0(m)+kR(n),k>mT(k)=T0m+k=1nmO(nk)=O(T0(m)+0nmnxdx)=O(T0(m)+nm).

若使用线性筛预处理前缀和,则 T0(m)=O(m)。由均值得:当 m=Θ(n23) 时,T(n) 取得最小值 O(n23)

最后,对于多组询问要使用记忆化,防止重复求 O(n) 种取值中的某一种。这样就可以保证复杂度是 O(n23)

应用

O(n) 跑不下来可以考虑尝试杜教筛。

μ

我们有恒等式

μ1=ϵ

启发我们取 g(n)=1(n)h(n)=ϵ(n)。所以有:

1(1)S(n)=i=1nϵ(i)i=2n1(i)S(ni)

即:

S(n)=1i=2n1(i)S(ni)

1(i) 的前缀和即为 i,直接杜教筛复杂度 O(n23)

φ

我们有恒等式

φ1=id

启发我们取 g(n)=1(n)h(n)=id(n)。所以有:

1(1)S(n)=i=1nid(i)i=2n1(i)S(ni)

即:

S(n)=n(n+1)2i=2n1(i)S(ni)

1(i) 的前缀和即为 i,直接杜教筛复杂度 O(n23)

代码实现

Luogu P4213 【模板】杜教筛 Link

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = (1 << 21) + 10, maxv = (1 << 21);
ll T, n;
vector<ll> primes, isp(maxn, true);
ll mu[maxn], phi[maxn];
unordered_map<int, ll> sphi;
unordered_map<int, int> smu;
void init() {
mu[1] = 1, phi[1] = 1;
for(int i = 2; i <= maxv; i++) {
if(isp[i]) {
primes.push_back(i);
mu[i] = -1, phi[i] = i - 1;
}
for(int p : primes) {
if(i * p > maxv) break;
isp[i * p] = false;
if(i % p) {
mu[i * p] = -mu[i], phi[i * p] = phi[i] * (p - 1);
}
else {
mu[i * p] = 0, phi[i * p] = phi[i] * p;
break;
}
}
}
for(int i = 1; i <= maxv; i++) mu[i] += mu[i - 1], phi[i] += phi[i - 1];
return;
}
ll get_phi(int x) {
if(x <= maxv) return phi[x];
if(sphi[x]) return sphi[x];
ll res = 1ll * x * (1ll * x + 1) / 2;
for(ll l = 2, r = 0; l <= x; l = r + 1) {
r = x / (x / l);
res -= get_phi(x / l) * (r - l + 1);
}
return sphi[x] = res;
}
int get_mu(int x) {
if(x <= maxv) return mu[x];
if(smu[x]) return smu[x];
ll res = 1;
for(ll l = 2, r = 0; l <= x; l = r + 1) {
r = x / (x / l);
res -= get_mu(x / l) * (r - l + 1);
}
return smu[x] = res;
}
int main() {
ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);
init();
cin >> T;
while(T--) {
cin >> n;
cout << get_phi(n) << " " << get_mu(n) << endl;
}
return 0;
}

完结撒花 😃

本文作者:Ydoc770

本文链接:https://www.cnblogs.com/Ydoc770/p/18703321

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Ydoc770  阅读(20)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起