积性函数前缀和(杜教筛)

积性函数前缀和,这东西似乎非常恐怖(特别是有公式恐惧症的人),也确实很恶心。
实际上,还是比较套路的,只要推公式时试图往套路上靠,推出来的概率就比较大。

看个例子来了解第一个套路

莫比乌斯函数的前缀和

利用性质

d|nμ(d)=[n=1]

可以得到
μ(n)=[n=1]d|n,d<nμ(d)

前缀和:
S(n)=i=1nμ(i)=1i=1nd|n,d<nμ(d)=1id=2nd=1nidμ(d)=1i=1nS(ni)

利用分块优化可以, ni=1 降为 O(n12) ,算上递归计算S,总时间复杂度 O(n12n14....)=O(n34) ( 18 太小忽略)
这还是过不到,我们可以提前 O(n) 预处理一段S,使得递归次数减少。
经玄学计算,只要预处理前 n23 个值,时间复杂度最低,为 O(n23)

代码:

#include<cstdio>
#include<map>
using namespace std;
const long long MAXN=5000000;

long long sumu[MAXN+10];
map<long long,long long> S;

void init_sumu()
{
    static long long mu[MAXN],prime[MAXN],pcnt=0;
    static bool npr[MAXN]={false};
    mu[1]=1;npr[1]=true;
    for(long long i=2;i<MAXN;i++)
    {
        if(!npr[i])
        {
            prime[++pcnt]=i;
            mu[i]=-1;
        }
        for(long long j=1;j<=pcnt&&1LL*prime[j]*i<MAXN;j++)
        {
            npr[i*prime[j]]=true;
            if(i%prime[j]==0)
            {
                mu[i*prime[j]]=0;
                break;
            }
            mu[i*prime[j]]=-mu[i];
        }
    }
    sumu[0]=0;
    for(long long i=1;i<MAXN;i++)
        sumu[i]=sumu[i-1]+mu[i];
}

long long solve(long long n)
{
    if(n<MAXN)
        return sumu[n];
    if(S.count(n))
        return S[n];
    long long res=1,nxt=0;
    for(long long k=2;k<=n;)
    {
        nxt=n/(n/k);
        res-=(nxt-k+1)*solve(n/k);
        k=nxt+1;
    }
    S[n]=res;
    return res;
}

int main()
{
    init_sumu();
    long long a,b;
    scanf("%lld%lld",&a,&b);
    printf("%lld\n",solve(b)-solve(a-1));
    return 0;
}

套路

试图把要求前缀和的函数 f ,使 d|nf(d) 为一个与d无关的值A,然后用 Ad|n,d<nf(d) ,即可得到 f(n) ,用这个公式求前缀和,既可以递归缩小规模,用杜教筛 O(n23) 解决。

猜你喜欢

转载自blog.csdn.net/can919/article/details/79358287
今日推荐