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