非常 NB 的一个筛法,虽然复杂度可能会趋近线性 $O(n ^ {1 - \epsilon})$,但是在 OI 范围内非常不错。
1. 算法介绍
要么是 $O(\dfrac{n ^ {\frac34}}{\log n})$,还有一种是 $O(n ^ {1 - \epsilon})$,反正可以在 $10^{10}$ 范围内跑 1s 左右。
2. 算法流程
感觉比杜教筛更好构造。
假设 $f(x)$ 的前缀和是我们要求的。
分为两步:先求出在当 $x \in P$(表示 $x$ 是质数)的 $f(x)$ 的前缀和,第二步通过质数的前缀和求出整个的前缀和。
1)求 $g(x) = f(x)[x \in P]$ 的前缀和
我们找到一个多项式 $g(x)$,使得 $x \in P$ 的时候 $f(x) = g(x)$。
定义 $\text{lpf}(x)$ 是 $x$ 的最小质因数,$p_x$ 表示第 $x$ 个质数。
先将多项式拆为多个 $x ^ k$,这样的话 $g(x)$ 就是一个完全积性函数了,即满足 $g(ab) = g(a) \times g(b)$。
我们定义 $h(n, k) = \sum_{i = 1}^n [i \in P \lor \text{lpf}(i) > p_k]g(i)$。
(显然状态数比 $n$ 还大)
我们考虑从 $h(…, k - 1)$ 转移到 $h(n, k)$。
考虑 $h(n, k - 1)$ 变为 $h(n, k)$,需要减去 $\text{lpf}(x) = p_k$ 的数。
如果我们将 $x$ 除以 $p_k$,又怎么样呢?
那么剩下的变为 $\text{lpf}(\dfrac x{p_k})\geq p_k$。至于为什么是 $\geq$,是因为可能有单个数可能有多个 $p_k$ 质因子。
这不和 $h(\dfrac n{p_k}, k - 1)$ 定义很像了吗!
对比一下:
其中为什么要加 $h(p_{k - 1}, p_{k - 1})$ 呢?因为 $h(\dfrac n{p_k}, k - 1)$ 包含了 $[1, \dfrac{n}{p_k}]$ 所有质数,而小于 $p_k$ 的质数我们前面的是没有计算到的,所以要加上 $[1, p_{k - 1}]$ 的所有质数的函数值,显然 $g(p_{k - 1}, p_{k - 1})$ 就可以满足要求。这样我们就可以得出:
又由于 $g$ 是完全积性函数(记得前面我们拆开多项式的目的吗?),我们可以得到这样的递推式:
按照这个递推,最后我们就可以得到所有质数位置的值的前缀和,即为 $h(n, k)$,其中 $p_k > \sqrt n$。
2)根据质数点的值求整个的前缀和
现在我们经过第一步的计算,已经知道了只有质数点的值 $g(x) = [x \in P]f(x)$ 的前缀,记为 $r(x)$。
我们定义 $S(n, k)$ 表示 $\displaystyle \sum_{i = 2}^n [\text{lpf}(i) > p_k]f(k)$。
类似刚刚推导 $h(n, k)$ 的方法,我们可以较为简单的求,注意是积性函数而不是完全积性函数。
我们考虑最小的质因数 $p_v > p_k$,然后枚举 $p_v$ 的指数。如是就可以从 $S(\dfrac n{p_v^e}, v)$ 转移,因为剩下的质因数都得大于 $p_v$。
注意我们的 $S(n, k)$ 没有定义 1,所以我们还要先加上 $> p_k$ 的质数的所有的和,这样我们就可以直接使用 $r(n) - h(p_k, k)$ 代替。
注意如果 $e \not= 1$,$p_v^e$ 本身就是合数,没有包含在 $r(n) - h(p_k, k)$ 中,所以我们要加上他们。
根据积性函数的定义,我们可以得到:
3. 代码实现
显然,如果一个数不是质数,那么 $\text{lpf}(n) \leq \sqrt n$,所以我们计算 $h(n, k)$ 的时候可以直接处理 $[1, \sqrt n]$ 的所有质数就可以了。
但是我们如果直接循环 $h(i, k)(i \in [1, n], p_k \leq \sqrt n)$,那么肯定暴力都跑不过。
可以发现,除了 $h(p_{k - 1}, k - 1)$,其余都是和 $n$ 有关。又有一个整除的小定理:
这说明,不管我们除什么,前面的 $h(n, k), h(\dfrac n{p_k}, k - 1)$ 都只会访问到 $\dfrac ni(i \in [1, n])$,而这个最多只有 $O(\sqrt n)$ 个取值。
这个实现可以直接整除分块,将可以访问到的值存到两个数组,可以表示为:
当然偷懒的话可以多一个 $\log$ 的 std::map
,但是似乎也不难,直接写整除分块了(本身算法就有点卡,还是不要给自己添麻烦吧 qwq
1 | using LL = long long; |
我们接着简单的分析复杂度:枚举质数 $O(\dfrac{\sqrt n}{\ln \sqrt n})$,枚举 $O(\sqrt n)$ 个数,前面时间复杂度为 $O(\dfrac n{\ln \sqrt n})$,显然跑不进 1s。
我们还有一个优化:在枚举 $h(n, k)$ 的时候,我们可以发现 $h(\dfrac n{p_k}, k - 1) - h(p_{k - 1}, k - 1)$ 有贡献时,肯定需要满足 $\dfrac{n}{p_k} \geq p_k$,否则 $h(\dfrac n{p_k}, k - 1)$ 就全是质数,并且只有 $[1, p_k)$ 中的质数,也就是 $h(p_{k - 1}, k - 1)$,那么后面贡献就是 0。所以我们只需枚举 $n$ 到 $p_k ^ 2$。
另外,$h(p_{k - 1}, k - 1)$ 最多也只有 $O(\dfrac{\sqrt n}{\ln \sqrt n})$ 个,我们直接预处理就可以了。这个我们记为 $sp_{k - 1}$。
(代码用的是 $g(n, k)$ 表示 $h(n, k)$,且只开了一维数组,因为访问的时候一定是标号小(即对应的 a[]
大)的从小的转移,可以用标号从小到大)
1 | for (int i = 0; i < cnt; ++ i) |
而对于后面是同样的,我们计算 $S(n, k)$ 时,如果 $p_v^2 > n$,只能枚举 $e = 1$,那么 $[e \not= 1] = 0$,$S(\dfrac{n}{p_v}, v)$ 由于 $\dfrac n{p_v} < p_v$ 所以就为 0,我们就无需枚举。
反正后面 $S(n, k)$ 是不需要记忆化的(跑得还挺快
最后注意 $S(n, 0) = \sum_{i = 2}^n f(i)$,返回这个,需要加 $f(1)$。
1 | LL S(LL n, int i) |
4. 例题
T1:区间素数个数
观察到这个函数并不是积性函数,所以我们似乎没法使用杜教筛等算法求解。
观察标签可得,我们可以发现,$h(n, x)(p_x > \sqrt n)$ 就是 $\sum_{i = 1}^n[i \in P]g(i)$,我们直接让 $g(x) = 1$,即可求出 $[1, n]$ 的质数个数即可。
点击查看代码
1 |
|
T2:简单的函数
首先有一个小学老师教的结论:偶质数只有 2。
那么,$f(i) = i\oplus 1 (i \in P)$ 就是:
我们就可以通过先筛 $g(i) = i,g(i) = 1$ 来得到 $f(i)(i \in P)$。
最后,我们计算 $\sum_{i = 1}^n f(i)$ 的时候,由于 $f(2) = 3$,所以我们在筛到 $2$ 的时候应该加上 2。
具体代码如下:
1 | LL S(LL n, int x)//x 从 0 开始标号 |
T3:DIVCNTK
要求 $\sum_{i = 1}^n d(i ^ k)\bmod 2^{64}$,其中 $d(x)$ 代表 $x$ 的约数个数。
我们可以简单的发现,$d(x ^ k)$ 是积性函数:$d(a^kb^k) = d(a^k)d(b^k) (a \perp b)$。
令 $f(x) = d(x ^ k)$,我们再尝试推一下 $f(p ^ c) = c(k + 1)$。发现这是一个简单的多项式,我们尝试拆开 $f(p^c) = c$,Min_25 筛即可。
代码不给了,留作练习题(逃