自己的一点(求积性函数前n项和)杜教筛总结(其实也是瞎抄的)

杜教筛可以解决哪一类的问题:


例如题目给你一个函数,让你求它的前项和 ∑ i = 1 n f ( i ) \sum_{i=1}^{n}f(i) i=1nf(i) ,但是这个n很大(n=1e10),显然在秒数级是不能算出来的,之前我们有学过拉格朗日插值定理求前n项和,它的复杂度与这个函数最高阶k紧密相连,差不多为O(k),但是你要想用拉格朗日插值法,你得知道这个函数f为多项式函数才行。比如让你求前n项欧拉函数和,莫比乌斯前n项和,你就用不上这个拉格朗日插值法了。


此时,为了解决这个问题,杜教筛就出来了,它就是用来求这些的。我们先大概说下他的原理我们先在毫秒复杂度的基础上预处理前T项和的结果(T<n),然后前n项和我们通过我们构造的函数表达式,用整数分块来去求解,此时复杂度会降为O( n 2 3 n^{\frac{2}{3}} n32)左右。


杜教筛也就是积性函数前n项和的参考博客:
神犇1
神犇2
下面写的如果有跟网上雷同,请不要介意,我只是写来给我以后回顾总结用的。

先认识下几个函数先:


函数f和g的卷积为 ( f ∗ g ) ( n ) = ∑ d ∣ n f ( d ) ∗ g ( n d ) (f*g)(n)=\sum_{d|n}f(d)*g(\frac{n}{d}) (fg)(n)=dnf(d)g(dn)
元函数 ϵ ( n ) \epsilon(n) ϵ(n),满足 ϵ ( n ) = [ n = = 1 ] \epsilon(n)=[n==1] ϵ(n)=[n==1]
恒等函数 I ( n ) I(n) I(n) ,满足恒等为1
单位函数 i d ( n ) id(n) id(n),满足 i d ( n ) = n id(n)=n id(n)=n

常用公式:

1, [ n = = 1 ] = ∑ d ∣ n u ( d ) [n==1]=\sum_{d|n}u(d) [n==1]=dnu(d)
2, ∑ i = 1 n [ g c d ( n , i ) = 1 ] ∗ i = n ∗ φ ( n ) + [ n = = 1 ] 2 \sum_{i=1}^{n}[gcd(n,i)=1]*i=\frac{n*\varphi(n)+[n==1]}{2} i=1n[gcd(n,i)=1]i=2nφ(n)+[n==1]
3, φ ( n ) = ∑ d ∣ n u ( d ) n d \varphi(n)=\sum_{d|n}u(d)\frac{n}{d} φ(n)=dnu(d)dn
4, φ ( n ) n = ∑ d ∣ n u ( d ) d \frac{\varphi(n)}{n}=\sum_{d|n}\frac{u(d)}{d} nφ(n)=dndu(d)
5, n = ∑ d ∣ n φ ( d ) n=\sum_{d|n}\varphi(d) n=dnφ(d)


杜教筛的通用表达式
已知我们要求函数f的前n项和,我们设 S ( n ) = ∑ i = 1 n f ( i ) S(n)=\sum_{i=1}^{n}f(i) S(n)=i=1nf(i),我们要找到函数g,并且函数f与函数g的卷积为 h = f ∗ g h=f*g h=fg,那么我们有如下表达式(证明见参考博客链接):
g ( 1 ) S ( n ) = ∑ i = 1 n h ( i ) − ∑ d = 2 n g ( d ) ∗ S ( ⌊ n d ⌋ ) g(1)S(n)=\sum_{i=1}^{n}h(i)-\sum_{d=2}^{n}g(d)*S(\left \lfloor \frac{n}{d} \right \rfloor) g(1)S(n)=i=1nh(i)d=2ng(d)S(dn)




通常我们会拿恒等函数 I I I给g去和f卷积。

例如1:求莫比乌斯前n项和: ∑ i = 1 n u ( i ) \sum_{i=1}^{n}u(i) i=1nu(i)
h = u ∗ I = ∑ d ∣ n u ( d ) = ϵ h=u*I=\sum_{d|n}u(d)=\epsilon h=uI=dnu(d)=ϵ(莫比乌斯函数性质)
故最终式子就为: S ( n ) = 1 − ∑ d = 2 n S ( ⌊ n d ⌋ ) S(n)=1-\sum_{d=2}^{n}S(\left \lfloor \frac{n}{d} \right \rfloor) S(n)=1d=2nS(dn)


例如2:求欧拉函数前n项和: ∑ i = 1 n φ ( i ) \sum_{i=1}^{n}\varphi(i) i=1nφ(i)
我们函数拿恒等函数给g去和f卷积,
h = φ ∗ I = ∑ d ∣ n φ ( d ) = i d ( n ) = n h=\varphi*I=\sum_{d|n}\varphi(d)=id(n)=n h=φI=dnφ(d)=id(n)=n (欧拉函数性质)
故最终的式子就为: S ( n ) = ∑ i = 1 n i − ∑ d = 2 n S ( ⌊ n d ⌋ ) S(n)=\sum_{i=1}^{n}i-\sum_{d=2}^{n}S(\left \lfloor \frac{n}{d} \right \rfloor) S(n)=i=1nid=2nS(dn)


例如2:题目链接:哆啦A梦传送门
题意:已知 n 2 − 3 ∗ n + 2 = ∑ d ∣ n f ( d ) n^2-3*n+2=\sum_{d|n}f(d) n23n+2=dnf(d)
∑ i = 1 n f ( i ) m o d ( 1 e 9 + 7 ) \sum_{i=1}^{n}f(i) \quad mod(1e9+7) i=1nf(i)mod(1e9+7) ( n ≤ 1 e 9 n\leq 1e9 n1e9)


这题是很模板的一道题,很显然我们也拿恒等函数 I ( n ) I(n) I(n)去和f卷积,得:
h = f ∗ I = ∑ d ∣ n f ( d ) = n 2 − 3 ∗ n + 2 h=f*I=\sum_{d|n}f(d)=n^2-3*n+2 h=fI=dnf(d)=n23n+2


故最终的式子就为:
S ( n ) = ∑ i = 1 n ( i 2 − 3 ∗ n + 2 ) − ∑ d = 2 n S ( ⌊ n d ⌋ ) = n ∗ ( n − 1 ) ∗ ( n − 2 ) 3 − ∑ d = 2 n S ( ⌊ n d ⌋ ) S(n)=\sum_{i=1}^{n}(i^2-3*n+2)-\sum_{d=2}^{n}S(\left \lfloor \frac{n}{d} \right \rfloor) =\frac{n*(n-1)*(n-2)}{3}-\sum_{d=2}^{n}S(\left \lfloor \frac{n}{d} \right \rfloor) S(n)=i=1n(i23n+2)d=2nS(dn)=3n(n1)(n2)d=2nS(dn)

我们设 g ( n ) = ∑ d ∣ n f ( i ) = i 2 − 3 ∗ i + 2 g(n)=\sum_{d|n}f(i)=i^2-3*i+2 g(n)=dnf(i)=i23i+2,由莫比乌斯反演可得:
f ( n ) = ∑ d ∣ n u ( d ) g ( n d ) f(n)=\sum_{d|n}u(d)g(\frac{n}{d}) f(n)=dnu(d)g(dn)
我们先预处理函数f前600000项来。
代码:

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;

typedef long long LL;

const LL mod=1e9+7;
LL n;
#define N 600000

LL sum[N+10],f[N+10];
bool vis[N+10];
int mu[N+10],tot,prime[N+10];

tr1::unordered_map<LL,LL> w;

void init()
{
    tot=0;
    mu[1]=1;
    for(int i=2;i<=N;i++)
    {
        if(!vis[i]){
            prime[++tot]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=tot&&prime[j]*i<=N;j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0) break;
            else mu[i*prime[j]]=-mu[i];
        }
    }
    LL item,t;
    for(int d=1;d<=N;d++)///枚举n的因子d
    {
        for(int n=d;n<=N;n+=d){ ///计算f(n)
                t=(LL)(n/d);
            item=(t*t%mod-3*t%mod+2+mod)%mod;///计算g(n/d)的值
            f[n]=(f[n]+item*mu[d]%mod+mod)%mod;

        }
    }

    ///预处理
    sum[0]=0;
    for(int i=1;i<=N;i++)
        sum[i]=(sum[i-1]+f[i])%mod;
}
int num=0;
LL djs(LL x)
{
    ///在预处理的范围内,直接返回
    if(x<=600000) return sum[x];
    if(w[x]) return w[x]; ///以及算好,直接返回

    LL t=x%mod;
    LL ans=t*(t-1)%mod*(t-2)%mod*333333336;
    for(LL l=2,r;l<=x;l=r+1)///整数分块
    {
        r=x/(x/l);
        ans=(ans-(r-l+1)*djs(x/l)%mod+mod)%mod;
        num++;
    }

    return w[x]=ans;
}

int main()
{

    init();

    int ncase;
    scanf("%d",&ncase);

    while(ncase--)
    {
        scanf("%lld",&n);
        printf("%lld\n",djs(n));

    }
    return 0;
}

我的标签:做个有情怀的程序员。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值