【模板】杜教筛

杜教筛利用狄利克雷卷积来构造积性函数前缀和之间的递推式,从而利用记忆化搜索在 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;
}

猜你喜欢

转载自www.cnblogs.com/wzj-xhjbk/p/10716019.html