杜教筛是一种在亚线性复杂度下快速得到积性函数前缀和的一种算法。
前置芝士:狄利克雷卷积、莫比乌斯反演(戳这里)
先从一个简单的问题入手
求(sumlimits_{i=1}^{n}f(i)),这里的(f(n))为一个积性函数,(n leq 10^9)
显然(n)的范围要求我们必须找到一个低于线性时间复杂度的算法。
定义(S(n) = sumlimits_{i=1}^{n}f(i)),再定义另一个积性函数(g),这个积性函数(g)的选择十分讲究,具体是什么需要结合具体问题来确定。
(f)和(g)的狄利克雷卷积的前缀和:
我们要求的是(S(n)),不妨先考虑(g(1)S(n)):
这个式子十分显然。
而根据上边前缀和的推导,又可以知道:
好的那么我们想求(S(n)),我们就需要求出两坨式子之差,左边那一坨是我们构造的函数,我们可以通过玄学的设计使得让(f*g)的前缀和尽可能好求。而右边那一坨,我们注意到其实跟(S(lfloor frac{n}{d}
floor))有关,那么就可以进行递归求解,结合上数论分块就搞定了。
现在我们就得到了杜教筛的套路式,根据这个式子套入函数,设计一个(g)函数就可以做了。
关于复杂度分析,假如全程递归求解的话,设求出(S(n))的复杂度是(T(n)),而根据套路式可知,想求出(S(n)),就需要知道(sqrt{n})个(S(lfloor frac{n}{i} floor)),因此有等式:
最后的得数是根据主定理得到的,不会的请参考算法导论(我也不会)
进一步优化,如果我们通过线性筛筛出前(m)个(S),那么复杂度可以进一步降低:
当(m=n^{frac{2}{3}})时,复杂度为(O(n^{frac{2}{3}}))
对于多组询问,如果用哈希表/unordered_map记录一下算过的答案可以获得常数优化。
下面是例题:
Luogu P4213模板题
求两个喜闻乐见的函数((mu)和(varphi))的前缀和。
先考虑(mu),首先代入套路式:
然后开始构造函数(g),因为我们知道,函数(u(n)=1)和(mu)在狄利克雷卷积中互为逆元,因此这里的(g)函数直接选择(u),这样左边那一坨直接就是1了。
这样就可以数论分块求解了。
ll get_mu(ll n)
{
if(n <= N - 10) return mu[n];
if(ansmu[n]) return ansmu[n];
ll ret = 1ll;
for(ll l = 2,r = 0;r < 2147483647 && l <= n;l = r + 1ll)
{
r = n / (n / l);
ret -= 1ll * (r - l + 1) * get_mu(n / l);
}
return ansmu[n] = ret;
}
求(varphi)稍稍难一丢丢。根据一个结论
转化成狄利克雷卷积形式,有
其中(id(n)=n)
这样代入套路式:
ll get_phi(ll n)
{
if(n <= N - 100) return phi[n];
if(ansphi[n]) return ansphi[n];
ll ret = 1ll * n * (n + 1) / 2;
for(ll l = 2,r = 0;r < 2147483647 && l <= n;l = r + 1ll)
{
r = n / (n / l);
ret -= 1ll * (r - l + 1) * get_phi(n / l);
}
return ansphi[n] = ret;
}
这样就可以AC模板题了!