0%

笔记 min_25筛

\(\mathcal O(n^{1-\epsilon})\) 复杂度内计算积性函数前缀和的算法,处理大概 \(10^{10}\) 级别的数据。

这东西从去年咕到了今年。

以下内容默认 \(p\in \text{prime}\)

min_25 筛要求积性函数 \(f(p^k)\) 可表示成 \(p\) 低阶多项式,例如 模板题 P5325\(f(p^k)=p^k(p^k-1)\)

基本思路

min_25 筛基本思路是把答案拆成质数和非质数两部分来分别计算。

第一部分

模板题 P5325 为例,拆开 \(f(p^k)=p^{2k}-p^k\),分别计算。

第一部分要余出来的是 \(g(n,i),sp(x)\)

\(sp(x)\) 为前 \(x\) 个质数的函数值之和,可直接预处理。

\(g(n,i)=\sum_{j=2}^n[j\in \text{prime}\lor \operatorname{minp}(j)>p_i]j^k\),其中 \(\operatorname{minp}(x)\) 表示 \(x\) 最小质因子。

可以发现 \(g(n,i)\) 是完全积性函数,并且在质数处的取值和 \(f(x)\) 一样。

然后递推 \(g(n,i)\)\[ g(n,i)=g(n,i-1)-p_i^k\Big(g(\lfloor\frac{n}{p_i}\rfloor,i-1)-g(p_{i-1},i-1)\Big) \] 后面就是提出一个 \(p_i^k\) 的质因子,因为是积性函数,所以不用提全。\(g(p_{i-1},i-1)\) 其实就是前 \(i-1\) 个质数的函数值之和。

这东西状态量多大呢?

从第一维看,根据定理 \(\displaystyle \Big\lfloor\frac{\lfloor\frac{n}{a}\rfloor}{b}\Big\rfloor=\lfloor\frac{n}{ab}\rfloor\),所以只要计算所有 \(\lfloor\frac{n}{x}\rfloor\) 的取值,可以除法分块,是 \(\mathcal O(\sqrt{n})\) 的;

从第二维看,由于非质数中大于 \(\sqrt n\) 的质因数一定不是最小质因子,那么当 \(p_i>\sqrt{n}\) 时,\(g(n,i)=g(n,i-1)\),所以我们只需要 \(\ge \sqrt{n}\) 的质数,大概 \(10^4\) 规模。

这一部分复杂度 \(\displaystyle \mathcal O(\frac{n^{\frac{2}{3}}}{\log n})\),并不会证。

实现有个细节,就是第一维的下标是 \(10^{10}\) 级别的存不下(这里用 map/unordered_map 就没什么优势了),但是可以根号分治:\(\lfloor\frac{n}{x}\rfloor\le \sqrt{n}\) 的存一个数组,\(\lfloor\frac{n}{x}\rfloor> \sqrt{n}\) 的按 \(x\) 存另一个数组。

第二部分

该开始统计答案了,设 \(S(n,i)=\sum_{j=2}^n[\operatorname{minp}(j)>p_i]f(j)\),最后要求的就是 \(S(n,0)\)

还是一样的思路,设 \(x\) 为最后一个小于等于 \(\sqrt{n}\) 的质数的下标。 \[ S(n,i)=g(n,x)-sp(i)+\sum_{k>i,p_k^c\le n}f(p_k^c)\Big(S(\lfloor\frac{n}{p_k^c}\rfloor,k)+[c\neq 1]\Big) \] 前面就是质数的贡献;后面是合数,因为不是完全积性了,所以得把质因子提全;另外 \([c\neq 1]\) 是把质数的幂算上。

这里不用记忆化,据说复杂度是 \(\mathcal O(n^{1-\epsilon})\),常数很小。

代码实现

P5325 code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
const int N = 1e6 + 9;
const LL P = 1e9 + 7;
const LL inv2 = 500000004;
const LL inv6 = 166666668;

LL n, sqr, cntp, tot;
LL pri[N], sp1[N], sp2[N], g1[N], g2[N], bas[N], id1[N], id2[N];
bool np[N];

inline void init_min_25() {
sqr = sqrt(n);
for (LL i = 2; i <= sqr; ++i) {
if (!np[i]) pri[++cntp] = i;
for (int j = 1; j <= cntp; ++j) {
LL k = i * pri[j];
if (k > sqr) break;
np[k] = true;
if (i % pri[j] == 0) break;
}
}
for (int i = 1; i <= cntp; ++i) {
sp1[i] = (sp1[i - 1] + pri[i]) % P;
sp2[i] = (sp2[i - 1] + pri[i] * pri[i] % P) % P;
}
for (LL l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
bas[++tot] = n / l;
LL t = bas[tot] % P;
g1[tot] = (t * (t + 1) % P * inv2 % P + P - 1) % P;
g2[tot] = (t * (t + 1) % P * (2 * t + 1) % P * inv6 % P + P - 1) % P;
if (bas[tot] <= sqr) id1[bas[tot]] = tot;
else id2[l] = tot;
}
for (int j = 1; j <= cntp; ++j) {
for (int i = 1; i <= tot && pri[j] * pri[j] <= bas[i]; ++i) {
LL t = bas[i] / pri[j];
t = (t <= sqr) ? id1[t] : id2[n / t];
g1[i] = (g1[i] - pri[j] * (g1[t] - sp1[j - 1] + P) % P + P) % P;
g2[i] = (g2[i] - pri[j] * pri[j] % P * (g2[t] - sp2[j - 1] + P) % P + P) % P;
}
}
}

LL S(LL i, int j) {
if (pri[j] >= i) return 0;
int id = (i <= sqr) ? id1[i] : id2[n / i];
LL res = (g2[id] - sp2[j] - (g1[id] - sp1[j]) + P + P) % P;
for (int k = j + 1; k <= cntp && pri[k] * pri[k] <= i; ++k) {
LL now = pri[k];
for (int c = 1; now <= i; ++c, now *= pri[k]) {
LL t = now % P;
res = (res + t * (t - 1 + P) % P * (S(i / now, k) + (c != 1)) % P) % P;
}
}
return res;
}

inline void main() {
scanf("%lld", &n);
init_min_25();
printf("%lld\n", (S(n, 0) + 1) % P);
}

一些题目

咕。