杜教筛

Posted Kananix

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了杜教筛相关的知识,希望对你有一定的参考价值。

如果您对下文中的某些步奏或名词尚未了解,可以参考我的前一篇文章 Here

杜教筛用于求积性函数前缀和                                                               

对于积性函数$ f$,令$ S(n)=\\sum\\limits_{i=1}^nf(i)$,求$ S(n)$   $( n<=10^9)$

我们找另一个积性函数$ g$, 对$ g$和$ f$做狄利克雷卷积,令结果为$ h$,

则有$ h(i)=\\sum\\limits_{d|i}g(d)f(\\frac{d}{i})$

对卷积结果$ h$计算前缀和$ t$,得$ t(n)=\\sum\\limits_{i=1}^n \\sum\\limits_{d|i}g(d)f(\\frac{i}{d})$

改为枚举$ d$,得$ t(n)=\\sum\\limits_{d=1}^ng(d) \\sum\\limits_{i=1}^\\frac{n}{d}f(i)$

后半部分用$ S$代替得$ t(n)=\\sum\\limits_{d=1}^ng(d)s(\\frac{n}{d})$

显然有$ g(1)S(n)=\\sum\\limits_{i=1}^ng(i)S(\\frac{n}{i})-\\sum\\limits_{i=2}^ng(i)S(\\left\\lfloor\\frac{n}{i}\\right\\rfloor)$

由于$ f$是积性函数,$ g(1)$恒等于1,化简可得

$ S(n)=t(n)-\\sum\\limits_{i=2}^ng(i)S(\\left\\lfloor\\frac{n}{i}\\right\\rfloor)$

发现式子最右侧明显可以数论分块

那么如果能够找到一个合理的积性函数$ g$,使得$ g$的前缀和与狄利克雷卷积的前缀和$ t$都很好计算,那么就可以快速递归计算前缀和$ S$了

 

Samples                                                                                              

求 $ \\sum\\limits_{i=1}^n \\mu(i)$   $ (i<=10^9)$  

我们需要找到一个合适的积性函数$ g$使得可以快速计算出$ \\sum\\limits_{i=1}^n g(i)$ 以及$ \\sum\\limits_{i=1}^n (g*\\mu)(i)$

由于$ \\mu*1=e$,我们可以令$ g=1$

那么化简模型式可得$ S(n)=1-\\sum\\limits_{i=2}^nS(\\frac{n}{i})$

我们可以线筛n<=500万的$ S(n)$,对于n>500万的进行记忆化搜索

总复杂度O(能过)  $ O(n^\\frac{2}{3})$

tips:记忆化的时候不需要使用map或者hash,因为搜索到的元素一定为$ n$的因数,所以对于$ 5000000<x<=10^9$的x我们只需要把答案存储在$ \\frac{n}{x}$的位置上即可

 

求 $ \\sum\\limits_{i=1}^n \\phi(i)$   $ (i<=10^9)$ 

同理,由于$ \\phi*1=id$ 我们继续令$ g=1$

化简模型式可得$ S(n)=\\frac{n(n+1)}{2}-\\sum\\limits_{i=2}^nS(\\frac{n}{i})$

同理线筛+记忆化计算即可

模板题:BZOJ3944 Here

代码:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rt register int
#define l putchar(\'\\n\')
#define ll long long
#define r read()
#define N 3000010
using namespace std;
inline ll read()
{
    register ll x = 0; char zf = 1; char ch;
    while (ch != \'-\' && !isdigit(ch)) ch = getchar();
    if (ch == \'-\') zf = -1, ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - \'0\', ch = getchar(); return x * zf;
}
void write(ll y)
{
    if (y < 0) putchar(\'-\'), y = -y;
    if (y > 9) write(y / 10);
    putchar(y % 10 + \'0\');
}
inline void writeln(const ll y){write(y);putchar(\'\\n\');}int i,j,k,m,n,x,y,z,cnt;
pair<ll,int>ret[10010];//记忆化
ll phi[N+10];int u[N+10];
pair<ll,int> anss(const int all,const unsigned x)//all用于记忆化,x表示求S(x),同时返回phi值和mu值
{
    if(x<N)return {phi[x],u[x]};
    if(ret[all/x].first)return ret[all/x];
    pair<ll,int> ans;ans={(ll)x*(x+1)>>1,1};
    for(register int i=2;i<=x;)//数论分块递归计算
    {
        const unsigned u=x/(x/i);
        const pair<ll,int>s=anss(all,x/i);
        ans.first-=(u-i+1)*s.first;
        ans.second-=(u-i+1)*s.second;
        i=u+1;
    }
    ret[all/x]=ans;
    return ans;
}
ll ss[800010];int top;bool pri[N+10];
int main()
{
    phi[1]=u[1]=1;
    for(rt i=2;i<=N;i++)//线筛初始化
    {
        if(!pri[i])ss[++top]=i,phi[i]=i-1,u[i]=-1;
        for(rt j=1;j<=top&&ss[j]*i<=N;j++)
        {
            pri[i*ss[j]]=1;
            phi[i*ss[j]]=phi[i]*(ss[j]-1);
            u[i*ss[j]]-=u[i];
            if(i%ss[j]==0)
            {
                phi[i*ss[j]]=phi[i]*ss[j];
                u[i*ss[j]]=0;
                break;
            }
        }
    }
    for(rt i=1;i<=N;i++)phi[i]+=phi[i-1];//计算前缀和
    for(rt i=1;i<=N;i++)u[i]+=u[i-1];
    for(rt t=r;t;t--)
    {
        x=r;memset(ret,0,sizeof(ret)); 
        pair<ll,int>ans=anss(x,x);
        printf("%lld %d\\n",ans.first,ans.second);
    }
    return 0;
}

 

以上是关于杜教筛的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 3944 Sum —— 杜教筛

杜教筛题表(已完成)

杜教筛学习笔记

hdu5608杜教筛

杜教筛

杜教筛