杜教筛利用狄利克雷卷积来构造积性函数前缀和之间的递推式,从而利用记忆化搜索在 n 的所有特殊点的前缀和处求得 n 处的前缀和。
杜教筛用来处理积性函数求和问题,时间复杂度为 \(O(n^{2\over 3})\)。
具体证明如下
\[\mu \star 1=\epsilon\]
\[\sum\limits_{i=1}^ni=\sum\limits_{k=1}^n\sum\limits_{d|k}\phi(d)=\sum\limits_{d=1}^n\sum\limits_{k=1}^{\lfloor{n\over d}\rfloor}\phi(k)\]
代码如下
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e6+10;
unordered_map<int,ll> _phi,_mu;
int n,mu[maxn],prime[maxn],tot;
ll phi[maxn];
bool vis[maxn];
void seive(){
mu[1]=phi[1]=1;
for(int i=2;i<=5e6;i++){
if(!vis[i])prime[++tot]=i,mu[i]=-1,phi[i]=i-1;
for(int j=1;i*prime[j]<=5e6;j++){
vis[i*prime[j]]=1;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}else{
mu[i*prime[j]]=-mu[i];
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
for(int i=1;i<=5e6;i++)mu[i]+=mu[i-1],phi[i]+=phi[i-1];
}
ll get_phi(ll x){
if(x<=5e6)return phi[x];
if(_phi[x])return _phi[x];
ll ret=x*(x+1)/2;
for(ll i=2,j;i<=x;i=j+1){
j=x/(x/i);
ret-=(j-i+1)*get_phi(x/i);
}
return _phi[x]=ret;
}
ll get_mu(ll x){
if(x<=5e6)return mu[x];
if(_mu[x])return _mu[x];
ll ret=1;
for(ll i=2,j;i<=x;i=j+1){
j=x/(x/i);
ret-=(j-i+1)*get_mu(x/i);
}
return _mu[x]=ret;
}
int main(){
seive();
int T;scanf("%d",&T);
while(T--){
scanf("%d",&n);
printf("%lld %lld\n",get_phi(n),get_mu(n));
}
return 0;
}
原文:https://www.cnblogs.com/wzj-xhjbk/p/10716019.html