所谓杜教筛,就是dms教给我们的筛
(逃)
前言
与其说算法,不如说是技巧。
可以在低于线性的时间复杂度(准确的说是 O(n23)O(n^{\frac{2}{3}})O(n32))内完成对积性函数的前缀和计算。
解析
考虑求函数 fff 的前缀和:
S(n)=∑i=1nf(i)S(n)=\sum_{i=1}^nf(i)S(n)=i=1∑nf(i)
寻找另一个函数 ggg,设 h=f∗gh=f*gh=f∗g,那么有:
∑i=1nh(i)=∑i=1n∑d∣ig(d)f(id)=∑d=1ng(d)∑i=1⌊nd⌋f(i)=∑d=1ng(d)S(⌊nd⌋)\sum_{i=1}^nh(i)=\sum_{i=1}^n\sum_{d|i}g(d)f(\frac i d)\\=\sum_{d=1}^ng(d)\sum_{i=1}^{\lfloor\frac n d\rfloor}f(i)\\=\sum_{d=1}^ng(d)S(\lfloor\dfrac n d\rfloor)i=1∑nh(i)=i=1∑nd∣i∑g(d)f(di)=d=1∑ng(d)i=1∑⌊dn⌋f(i)=d=1∑ng(d)S(⌊dn⌋)。
尝试凑出 S(n)S(n)S(n):
g(1)S(n)=∑d=1ng(d)S(⌊nd⌋)−∑d=2ng(d)S(⌊nd⌋)=∑d=1nh(d)−∑d=2ng(d)S(⌊nd⌋)g(1)S(n)=\sum_{d=1}^ng(d)S(\lfloor\dfrac n d\rfloor)-\sum_{d=2}^ng(d)S(\lfloor\dfrac n d\rfloor)\\=\sum_{d=1}^nh(d)-\sum_{d=2}^ng(d)S(\lfloor\dfrac n d\rfloor)g(1)S(n)=d=1∑ng(d)S(⌊dn⌋)−d=2∑ng(d)S(⌊dn⌋)=d=1∑nh(d)−d=2∑ng(d)S(⌊dn⌋)
后面的 SSS 就可以递归求解了。
时间复杂度
按照上面的式子,可以写出时间复杂度的递推式:
T(n)=O(n)+∑i=2n(T(i)+T(ni))T(n)=O(\sqrt n)+\sum_{i=2}^{\sqrt n}(T(i)+T(\frac n i))T(n)=O(n)+i=2∑n(T(i)+T(in))
由于再往下递归就是高阶小量,我们只需要展开一层:
T(n)=O(n)+∑i=2n(O(i)+O(ni))T(n)=O(\sqrt n)+\sum_{i=2}^{\sqrt n}(O(\sqrt i)+O(\sqrt \frac n i))T(n)=O(n)+i=2∑n(O(i)+O(in))
由于 O(i)+O(ni)≥O(2n)=O(n14)O(\sqrt i)+O(\sqrt \frac n i)\ge O(2\sqrt{\sqrt n})=O(n^{\frac 1 4})O(i)+O(in)≥O(2n)=O(n41),所以总的复杂度大概是 O(n34)O(n^{\frac 3 4})O(n43)。
考虑先用线性筛预处理出一部分前缀和。
由于算法本身根号分治就有 O(n)O(\sqrt n)O(n),所以不妨设预处理的范围 m>nm>\sqrt nm>n。
那么时间复杂度就变成了:
T(n)=O(m)+O(n)+∑i=2⌊nm⌋T(⌊ni⌋)=O(m)+O(n)+∑i=2⌊nm⌋O(⌊ni⌋)=O(m)+O(n)+∫0nmnx=O(m)+O(n)+f(nm)(f(x)=nx)=O(m)+O(n)+O(nm)≥O(n23)T(n)=O(m)+O(\sqrt n)+\sum_{i=2}^{\lfloor\frac n m\rfloor}T(\lfloor\frac n i\rfloor)\\=O(m)+O(\sqrt n)+\sum_{i=2}^{\lfloor\frac n m\rfloor}O(\sqrt{\lfloor\frac n i\rfloor})\\=O(m)+O(\sqrt n)+\int_0^{\frac n m}\sqrt{\frac n x}\\=O(m)+O(\sqrt n)+f(\frac n m)(f(x)=\sqrt{nx})\\=O(m)+O(\sqrt n)+O(\frac{n}{\sqrt m})\\\ge O(n^{\frac 2 3})T(n)=O(m)+O(n)+i=2∑⌊mn⌋T(⌊in⌋)=O(m)+O(n)+i=2∑⌊mn⌋O(⌊in⌋)=O(m)+O(n)+∫0mnxn=O(m)+O(n)+f(mn)(f(x)=nx)=O(m)+O(n)+O(mn)≥O(n32)
当 m=n23m=n^\frac 2 3m=n32 时取等。
应用
如何使用杜教筛呢?
举个例子:计算 ∑i=1nμ(i)\sum_{i=1}^n\mu(i)∑i=1nμ(i)。
我们把刚才的核心式子拿下来:
g(1)S(n)=∑d=1nh(d)−∑d=2ng(d)S(⌊nd⌋)g(1)S(n)=\sum_{d=1}^nh(d)-\sum_{d=2}^ng(d)S(\lfloor\dfrac n d\rfloor)g(1)S(n)=d=1∑nh(d)−d=2∑ng(d)S(⌊dn⌋)
首先,我们需要使 ∑d=1nh(d)\sum_{d=1}^nh(d)∑d=1nh(d) 可以很容易的求出来,不然就没有意义了。
同时,我们最好也能让 ggg 函数简单一些。
想到:μ∗1=e\mu*1=eμ∗1=e。
令 g=1,h=eg=1,h=eg=1,h=e,就很好的满足了我们的要求。
式子变成:
S(n)=1−∑d=2nS(⌊nd⌋)S(n)=1-\sum_{d=2}^nS(\lfloor\dfrac n d\rfloor)S(n)=1−d=2∑nS(⌊dn⌋)
非常简洁,直接求解即可。
实现上,需要开一个 map
或者哈希表进行记忆化。
代码
ll getmu(int n){if(n<=w) return sum1[n];if(mp1.count(n)) return mp1[n];ll ans=1;for(ll l=2,r;l<=n;l=r+1){assert(n/l);r=n/(n/l);ans-=(r-l+1)*getmu(n/l);}return mp1[n]=ans;
}