前言
杜教筛是一个用来快速求积性函数前缀和的方法,其时间复杂度可达到
O
(
n
2
3
)
O(n^{\frac{2}{3}})
O(n32),进而可以处理1e9的问题,其原理如下:
设
待
求
和
式
为
S
(
n
)
=
∑
i
=
1
n
f
(
i
)
,
n
=
1
e
9
设待求和式为 S(n)= \sum_{i=1}^{n}f(i), \space n=1e9
设待求和式为S(n)=i=1∑nf(i), n=1e9我们的工作是找一个可在
O
(
1
)
O(1)
O(1)时间内查询的积性函数
g
g
g,那么它与待求和式有如下关系:
∑
i
=
1
n
(
f
∗
g
)
(
i
)
=
∑
i
=
1
n
g
(
i
)
S
(
⌊
n
i
⌋
)
\sum_{i=1}^n(f*g)(i)=\sum_{i=1}^ng(i)S(\lfloor \frac{n}{i} \rfloor)
i=1∑n(f∗g)(i)=i=1∑ng(i)S(⌊in⌋)
简证:
∑
i
=
1
n
(
f
∗
g
)
(
i
)
\sum_{i=1}^n(f*g)(i)
i=1∑n(f∗g)(i)
=
∑
i
=
1
n
∑
d
∣
i
g
(
d
)
f
(
i
d
)
=\sum_{i=1}^n\sum_{d|i}g(d)f(\frac{i}{d})
=i=1∑nd∣i∑g(d)f(di)
=
∑
i
=
1
n
∑
j
=
1
⌊
n
i
⌋
g
(
i
)
f
(
j
)
=\sum_{i=1}^n\sum_{j=1}^{\lfloor \frac{n}{i} \rfloor}g(i)f(j)
=i=1∑nj=1∑⌊in⌋g(i)f(j)
=
∑
i
=
1
n
g
(
i
)
∑
j
=
1
⌊
n
i
⌋
f
(
j
)
=\sum_{i=1}^ng(i)\sum_{j=1}^{\lfloor \frac{n}{i} \rfloor}f(j)
=i=1∑ng(i)j=1∑⌊in⌋f(j)
=
∑
i
=
1
n
g
(
i
)
S
(
⌊
n
i
⌋
)
=\sum_{i=1}^ng(i)S(\lfloor \frac{n}{i} \rfloor)
=i=1∑ng(i)S(⌊in⌋)进而可以得到一个方便计算的递推式:
g
(
1
)
S
(
n
)
=
∑
i
=
1
n
(
f
∗
g
)
(
i
)
−
∑
i
=
2
n
g
(
i
)
S
(
⌊
n
i
⌋
)
g(1)S(n)=\sum_{i=1}^n(f*g)(i) - \sum_{i=2}^ng(i)S(\lfloor \frac{n}{i} \rfloor)
g(1)S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)S(⌊in⌋)
我们来分析一下它的时间复杂度,假设我们已预处理过
[
1
,
n
]
[1,\sqrt{n}]
[1,n],那么
[
1
,
n
]
[1,\sqrt{n}]
[1,n]内的前缀和的前缀和的查询为
O
(
t
)
O(\sqrt{t})
O(t):
O
(
S
(
n
)
)
=
O
(
1
)
−
O
(
∫
2
n
n
x
d
x
)
=
O
(
n
3
4
)
O(S(n))=O(1)-O(\int_2^{\sqrt{n}}\sqrt{\frac{n}{x}}dx)=O(n^{\frac{3}{4}})
O(S(n))=O(1)−O(∫2nxndx)=O(n43)这个复杂度不错,但我们可以继续改进,预处理
[
1
,
n
2
3
]
[1,n^{\frac{2}{3}}]
[1,n32]的前缀和,则可以得到
O
(
n
2
3
)
O(n^{\frac{2}{3}})
O(n32)的复杂度,证明方法同上
下面给出几个常见的辅助函数:
S
(
i
)
=
∑
i
=
1
n
μ
(
i
)
,
g
(
i
)
=
I
(
i
)
S(i)=\sum_{i=1}^{n}\mu(i), g(i)=I(i)
S(i)=i=1∑nμ(i),g(i)=I(i)
S
(
i
)
=
∑
i
=
1
n
φ
(
i
)
,
g
(
i
)
=
I
(
i
)
S(i)=\sum_{i=1}^{n}\varphi(i), g(i)=I(i)
S(i)=i=1∑nφ(i),g(i)=I(i)
S
(
i
)
=
∑
i
=
1
n
i
2
φ
(
i
)
,
g
(
i
)
=
i
2
S(i)=\sum_{i=1}^{n}i^2\varphi(i), g(i)=i^2
S(i)=i=1∑ni2φ(i),g(i)=i2
一、例题
二、代码及思路
1.思路
直接用 ϕ \phi ϕ 和 μ \mu μ 的板子即可
2.代码
代码如下:
#include <iostream>
#include <map>
#define int long long
using namespace std;
const int maxn = 5e6;
int miu[maxn], phi[maxn];
int premiu[maxn], prephi[maxn];
int prime[maxn], pn;
bool isp[maxn];
map<int, int> Smiu;
map<int, int> Sphi;
void table() {
for (int i = 1; i < maxn; i++) isp[i] = true;
miu[1] = phi[1] = 1, isp[1] = false;
for (int i = 2; i < maxn; i++) {
if (isp[i]) {
prime[pn++] = i;
miu[i] = -1, phi[i] = i - 1;
}
for (int j = 0; j < pn && i * prime[j] < maxn; j++) {
isp[i * prime[j]] = false;
if (i % prime[j] == 0) {
miu[i * prime[j]] = 0;
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
miu[i * prime[j]] = -miu[i];
phi[i * prime[j]] = phi[i] * phi[prime[j]];
}
}
for (int i = 1; i < maxn; i++)
premiu[i] = miu[i] + premiu[i - 1], prephi[i] = phi[i] + prephi[i - 1];
}
int getmiu(int n) {
if (n < maxn) return premiu[n];
if (Smiu[n]) return Smiu[n];
int res = 1;
for (int l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
res -= (r - l + 1) * getmiu(n / l);
}
return Smiu[n] = res;
}
int getphi(int n) {
if (n < maxn) return prephi[n];
if (Sphi[n]) return Sphi[n];
int res = n * (n + 1) / 2;
for (int l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
res -= (r - l + 1) * getphi(n / l);
}
return Sphi[n] = res;
}
signed main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
int t, n;
table();
scanf("%lld", &t);
while (t--) {
scanf("%lld", &n);
printf("%lld %lld\n", getphi(n), getmiu(n));
}
return 0;
}