前置要求
杜教筛
一部分数论题会问一个数论函数的前缀和,不妨令其为 (S(n)=sumlimits_{i=1}^n f(i)) 。有时直接求会比较困难。
杜教筛是通过构建一个函数 (g) , (f*g) 的前缀和能快速( (O(1)) )求出。这样能在低于线性的复杂度内求解 (S(n)) 。
具体地来说就是:
那么
好像还是没有讲清楚……
举个例子:
比方说 (f=mu) , (S(n)=sumlimits_{i=1}^n f(i)) 。那么可以构造 (g=id_0) ,那么有
可以得到
时间复杂度
计算 (S(n)) 的时候,需要知道 (S(frac{n}{i}),iin (2, n)) 。这个可以进行整除分块。也就是说,假设我们已经知道了 (S(1cdots n-1)) ,那么计算 (S(n)) 的复杂度是 (O(sqrt{n})) 的。
将 (S(n)) 分为两部分考虑。 当 (igeqslant sqrt{n}) 时, (frac{n}{i}leqslant n) 。那么假设把 (S(1cdotssqrt{n})) 全算了,时间复杂度是 (O(sumlimits_{i=1}^{sqrt{n}} sqrt{i})) 的。
再考虑 (ileqslant sqrt{n}) 的情况。由于 (lfloorfrac{lfloorfrac{n}{i} floor}{j} floor=lfloorfrac{n}{ij} floor) ,那么下标大于 (sqrt{n}) 的 (S) 在递归过程中 总共 只会被访问到 (O(sqrt{n})) 个。那么求出这 (O(sqrt{n})) 个的时间复杂度是 (O(sumlimits_{i=1}^{sqrt{n}} sqrt{frac{n}{i}})) 。
后一部分的复杂度显然大于前一部分。所以总的时间复杂度是
而实际上可以做得更好。前面计算第一部分的时候,假设了计算了所有的 (S(1cdotssqrt{n})) 。但最后计算的时候,仍然是舍去的。不妨考虑稍微多预处理一点,来换取总的较小的复杂度。
根据前人经验,用线性筛预处理前 (n^{frac{2}{3}}) 项。那么预处理的时间复杂度是 (O(n^{frac{2}{3}})) 。然后后面一部分的复杂度就变为了
优化常数和空间
由于上面提到的, (lfloorfrac{lfloorfrac{n}{i} floor}{j} floor=lfloorfrac{n}{ij} floor) ,需要求出的 (S) 的下标都是 (n) 的约数。所以可以将下标大于 (sqrt{n}) 的 (S) 存在另一个数组的 (frac{n}{i}) 里。这样就省去了 hash 或者 map 。