杜教筛学习笔记
本来想放在 数论初步 里的,但是那篇文章实在太长了,编辑的时候标签页崩溃了。。。
上面的文章为前置知识。
参考资料:
理论基础
杜教筛是一种可以在 \(\mathcal O(n^{\frac 23})\) 的时间内算出一个积性函数的前缀和的算法。
数论基础
设有一个积性函数 \(\mathbf f\),它的前缀和为 \(\mathbf S(n)=\sum_{i=1}^n\mathbf f(i)\)。再找一个积性函数 \(\mathbf g\),考虑它们狄利克雷卷积的前缀和,可得:
则有
由于 \(\mathbf g\) 是积性函数,所以 \(\mathbf g(1)=1\)。因此
如果我们可以快速计算 \(\mathbf {f*g}\) 和 \(\mathbf g\) 的前缀和,就可以利用数论分块递归计算 \(\mathbf S\)。另外,为了优化时间复杂度,可以 \(\mathcal O(n^{\frac 23})\) 预处理出前 \(n^{\frac 23}\) 个数的答案,之后再用杜教筛。然而我不会证明时间复杂度(不会微积分太不方便了)。 现在会了。见下文。
对于记录答案,可以用 map,但更好的做法是观察性质。我们知道
证明:设 \(x=ay+b(b<y)\),\(a=cz+d(d<z)\),则左式显然为 \(c\),而 \(x=(cz+d)y+b=cyz+dy+b\),又因为 \(dy+b\le (z-1)y+y-1<yz\),所以右式也等于 \(c\),得证。
所以每次递归计算的一定是某个 \(\mathbf S(\lfloor\dfrac nx\rfloor)\)。由于当 \(x>n^{\frac 13}\) 时会直接获取预处理出的答案,所以需要记忆化的数一定少于 \(n^{\frac 13}\),因此可以开一个哈希表记录答案。
所以我们要设计一个哈希函数 \(H\),保证对于任意的 \(\lfloor\dfrac nx\rfloor\ne \lfloor\dfrac ny\rfloor\land x,y\le n^{\frac 13}\),都有 \(H(\lfloor\dfrac nx\rfloor)\ne H(\lfloor\dfrac ny\rfloor)\)。可以取 \(H(i)=\lfloor\dfrac ni\rfloor\)。因为
是使得取值为 \(\lfloor\dfrac nk\rfloor=\lfloor\dfrac nx\rfloor\) 的最大的 \(k\)(证明见前置知识中的数论分块部分),所以 \(\lfloor\dfrac nx\rfloor\) 和 \(H(\lfloor\dfrac nx\rfloor)\) 构成了一个双射。
时间复杂度证明
我们已经证明了 \(\mathbf S\) 的自变量取值一定是某个 \(\lfloor\dfrac nx\rfloor\),所以只有 \(O(\sqrt n)\) 种状态。最多的时候状态集合为 \(\{x\mid x\in\mathbb N\cap[1,\sqrt n]\}\cap\{\lfloor\dfrac nx\rfloor\mid x\in\mathbb N\cap[1,\sqrt n]\}\)。而计算一个状态 \(x\) 的时间复杂度为 \(O(\sqrt x)\)。所以如果不预处理,总的时间复杂度为
如果线性预处理出 \([1,n^a]\) 的答案且 \(a>\dfrac 12\),则时间复杂度为
可以看出,\(a\) 取 \(\dfrac 23\) 时最优,时间复杂度为 \(O(n^{\frac 23})\)。
代码实现
知道杜教筛的理论后,只要知道 \(\varphi*\mathbf 1=\mathbf{id}\),\(\mu*\mathbf 1=\varepsilon\),以及数据的毒瘤程度,就可以写出代码了:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1.7e6,sN=1300;
int _n,v[N],phi[N],mu[N];
ll preP[N],preM[N],sumP[sN],sumM[sN];
vector<int>pr;
inline void init(){
preP[1]=phi[1]=1;preM[1]=mu[1]=1;
for(int i=2;i<N;i++){
if(!v[i])pr.push_back(v[i]=i),mu[i]=-1,phi[i]=i-1;
for(int p:pr){
if(p>v[i]||p>(N-1)/i)break;
v[i*p]=p;if(v[i]==p)phi[i*p]=phi[i]*p,mu[i*p]=0;
else phi[i*p]=phi[i]*phi[p],mu[i*p]=mu[i]*mu[p];
}
preP[i]=preP[i-1]+phi[i];preM[i]=preM[i-1]+mu[i];
}
}
ll getSumPhi(int n){
if(n<N)return preP[n];
if(sumP[_n/n])return sumP[_n/n];
ll ret=((ll)n*(n+1))>>1;for(int l=2,r=1;l<=n;l=r+1){
r=n/(n/l);
ret-=getSumPhi(n/l)*(r-l+1);
}
return sumP[_n/n]=ret;
}
ll getSumMu(int n){
if(n<N)return preM[n];
if(sumM[_n/n])return sumM[_n/n];
ll ret=1;for(int l=2,r=1;l<=n;l=r+1){
r=n/(n/l);
ret-=getSumMu(n/l)*(r-l+1);
}
return sumM[_n/n]=ret;
}
inline void ct(){
scanf("%d",&_n);
if(_n==2147483647){// 当 n=(1<<31)-1 时,int 会爆,所以要特判
printf("%lld %d\n",1401784457568941916,9569);
return;
}
memset(sumP,0,sizeof(sumP));
memset(sumM,0,sizeof(sumM));
printf("%lld %lld\n",getSumPhi(_n),getSumMu(_n));
}
int main(){
init();int T;scanf("%d",&T);
while(T--)ct();
return 0;
}

浙公网安备 33010602011771号