杜教筛学习记录

杜教筛

本文参考pengym的杜教筛
杜教筛需要一些预备知识:数论分块(可以参考之前的文章ACM数论初步)、积性函数、狄利克雷卷积,最好了解莫比乌斯函数

积性函数

对于数论函数 f ( x ) f(x) f(x),如果对于任意满足 gcd ⁡ ( x , y ) = 1 \gcd(x,y)=1 gcd(x,y)=1 x , y x,y x,y,有 f ( x ⋅ y ) = f ( x ) ⋅ f ( y ) f(x\cdot y)=f(x)\cdot f(y) f(xy)=f(x)f(y),则称 f ( x ) f(x) f(x)积性函数;如果去掉互质的条件仍能满足后面的性质成立,则称 f ( x ) f(x) f(x)完全积性函数

几个常见的积性函数:

  1. μ ( n ) \mu(n) μ(n)——莫比乌斯函数,在莫比乌斯反演讲过了
  2. φ ( n ) \varphi(n) φ(n)——欧拉函数,表示 [ 1 , n − 1 ] [1,n-1] [1,n1]中与 n n n互质的数的个数( [ 1 , n ] [1,n] [1,n]也一样的,反正 n n n n n n也不互质)
  3. d ( n ) d(n) d(n)——约数个数函数,表示 n n n的约数个数, d ( n ) = ∑ d ∣ n 1 = ∑ d = 1 n [ d ∣ n ] d(n)=\sum\limits_{d|n}1=\sum\limits_{d=1}^{n}[d|n] d(n)=dn1=d=1n[dn]
  4. σ ( n ) \sigma(n) σ(n)——约数和函数,表示 n n n的各个约数之和, σ ( n ) = ∑ d ∣ n d = ∑ d = 1 n [ d ∣ n ] ∗ d \sigma(n)=\sum\limits_{d|n}d=\sum\limits_{d=1}^{n}[d|n]*d σ(n)=dnd=d=1n[dn]d

几个常见的完全积性函数:

  1. ϵ ( n ) \epsilon(n) ϵ(n)——元函数,只有 n = 1 n=1 n=1时函数值为1,其他情况为0, ϵ ( n ) = [ n = = 1 ] \epsilon(n)=[n==1] ϵ(n)=[n==1]
  2. I ( n ) I(n) I(n)——恒等函数,函数值恒为1
  3. i d ( n ) id(n) id(n)——单位函数, i d ( n ) = n id(n)=n id(n)=n

积性函数还有一个重要的性质:积性函数 ∗ * 积性函数仍为积性函数,这个性质利用积性函数的定义可以很容易地推出

狄利克雷卷积

定义

两个数论函数 f f f g g g的卷积为 ( f ∗ g ) ( n ) = ∑ d ∣ n f ( d ) ⋅ g ( n d ) (f*g)(n) = \sum\limits_{d|n}f(d) \cdot g(\dfrac{n}{d}) (fg)(n)=dnf(d)g(dn),读作 f f f g g g,其实读 g g g f f f也是一样的,因为有定义很容易看出狄利克雷卷积有交换律(本文中的 ′ ∗ ′ '*' 均为狄利克雷卷积, ′ ⋅ ′ '\cdot' 为通常的乘法)

性质

  1. 交换律: f ∗ g = g ∗ f f*g=g*f fg=gf
  2. 结合律: ( f ∗ g ) ∗ h = f ∗ ( g ∗ h ) (f*g)*h=f*(g*h) (fg)h=f(gh)
  3. 分配律: ( f + g ) ∗ h = f ∗ h + g ∗ h (f+g)*h=f*h+g*h (f+g)h=fh+gh,严格来讲是卷积运算对 '+'运算满足分配律

读者们看到 ϵ ( n ) \epsilon(n) ϵ(n)可能觉得这种函数啥用?从近世代数的角度看,记所有数论函数的集合为 S S S S S S和狄利克雷卷积 ∗ * 就构成了一个代数系 ( S , ∗ ) (S,*) (S,),而 ϵ ( n ) \epsilon(n) ϵ(n)就是这个代数系的单位元,由于狄利克雷卷积 ∗ * 满足结合律和交换律,所以 ( S , ∗ ) (S,*) (S,)是个可换半群,再加上单位元, ( S , ∗ , ϵ ) (S,*,\epsilon) (S,,ϵ)就是一个可换幺半群,至于它是否为一个群在此不作讨论(近世代数的内容看不懂也没关系,不影响后面的内容,笔者也只是刚学一点,而且学得极烂)

狄利克雷卷积可以用来证明一些性质,例如:

  • 莫比乌斯函数 μ \mu μ

    莫比乌斯函数有一个重要性质: ∑ d ∣ n μ ( d ) = [ n = = 1 ] \sum\limits_{d|n}\mu(d)=[n==1] dnμ(d)=[n==1],下面我们用狄利克雷卷积证明莫比乌斯反演:


    由已知得
    F ( n ) = ∑ d ∣ n f ( d ) , F(n) = \sum\limits_{d|n}f(d), F(n)=dnf(d) 等价于 F = f ∗ I F=f*I F=fI,两边同时卷积莫比乌斯函数 μ \mu μ,得
    F ∗ μ = ( f ∗ I ) ∗ μ F*\mu=(f*I)*\mu Fμ=(fI)μ 由结合律得
    F ∗ μ = ( f ∗ I ) ∗ μ = f ∗ ( I ∗ μ ) = f ∗ ∑ d ∣ n I ( d ) ⋅ μ ( n d ) = f ∗ ∑ d ∣ n μ ( n d ) {F*\mu\\ =(f*I)*\mu\\ =f*(I*\mu)\\ =f*\sum_{d|n}I(d)\cdot \mu(\dfrac{n}{d})\\ =f*\sum_{d|n}\mu(\dfrac{n}{d}) } Fμ=(fI)μ=f(Iμ)=fdnI(d)μ(dn)=fdnμ(dn) 再根据 μ \mu μ的性质 ∑ d ∣ n μ ( d ) = [ n = = 1 ] \sum\limits_{d|n}\mu(d)=[n==1] dnμ(d)=[n==1],上式又化为 f ∗ ϵ f*\epsilon fϵ,就等于 f f f,所以就有
    f = F ∗ μ = ∑ d ∣ n F ( d ) ⋅ μ ( n d ) f=F*\mu=\sum_{d|n}F(d)\cdot\mu(\dfrac{n}{d}) f=Fμ=dnF(d)μ(dn)


    采用狄利克雷卷积的证明简单易懂,比之前的和式变换直观许多

  • 欧拉函数 φ \varphi φ

    欧拉函数有一个重要的性质: ∑ d ∣ n φ ( d ) = n \sum\limits_{d|n}\varphi(d) = n dnφ(d)=n,用它和狄利克雷卷积,我们可以证明莫比乌斯函数的一个性质: φ ( n ) n = ∑ d ∣ n μ ( d ) d \dfrac{\varphi(n)}{n}=\sum\limits_{d|n}\dfrac{\mu(d)}{d} nφ(n)=dndμ(d)


    由已知
    ∑ d ∣ n φ ( d ) = n \sum_{d|n}\varphi(d) = n dnφ(d)=n 将它表示为狄利克雷卷积形式: φ ∗ I = i d \varphi*I=id φI=id,式子两边同时卷 μ \mu μ,得
    φ ∗ I ∗ μ = i d ∗ μ    ⟺    φ ∗ ϵ = i d ∗ μ    ⟺    φ = i d ∗ μ \varphi*I*\mu=id*\mu\\ \iff \varphi*\epsilon=id*\mu\\ \iff \varphi=id*\mu φIμ=idμφϵ=idμφ=idμ 所以 φ ( n ) = ∑ d ∣ n μ ( d ) ⋅ n d \varphi(n)=\sum\limits_{d|n}\mu(d)\cdot\dfrac{n}{d} φ(n)=dnμ(d)dn,移项得
    φ ( n ) n = ∑ d ∣ n μ ( d ) d \dfrac{\varphi(n)}{n} = \sum_{d|n} \dfrac{\mu(d)}{d} nφ(n)=dndμ(d)


杜教筛

掌握了前面的知识以后,就可以开始杜教筛的学习了。首先,我们应该清楚一个问题:杜教筛到底是用来干什么的?杜教筛是以低于线性时间复杂度计算出积性函数前缀和的筛法,需要注意的是它筛的不是 [ 1 , n ] [1, n] [1,n]这么大的范围,它只筛 n n n,不过能在筛 n n n能顺路筛出一些其他的(具体看下文吧

也就是说我们要求 ∑ i = 1 n f ( i ) \sum\limits_{i=1}^n f(i) i=1nf(i),其中 f ( i ) f(i) f(i)是积性函数,我们姑且记 ∑ i = 1 n f ( i ) = S ( n ) \sum\limits_{i=1}^nf(i)=S(n) i=1nf(i)=S(n)

为了解决上面的问题,我们构造两个积性函数 h h h g g g,使得 h = f ∗ g h=f*g h=fg,下面我们开始求 ∑ i = 1 n h ( i ) \sum\limits_{i=1}^n h(i) i=1nh(i)
∑ i = 1 n h ( i ) = ∑ i = 1 n ∑ d ∣ i g ( d ) ⋅ f ( i d ) = ∑ d = 1 n g ( d ) ⋅ ∑ i = 1 ⌊ n d ⌋ f ( i ) = ∑ d = 1 n g ( d ) ⋅ S ( ⌊ n d ⌋ ) \begin{aligned} \sum_{i=1}^n h(i) & =\sum_{i=1}^n\sum_{d|i}g(d)\cdot f(\dfrac{i}{d})\\ & = \sum_{d=1}^n g(d)\cdot \sum_{i=1}^{\left\lfloor \tfrac{n}{d}\right\rfloor} f(i)\\ & = \sum_{d=1}^ng(d)\cdot S(\left\lfloor \frac{n}{d} \right\rfloor) \end{aligned} i=1nh(i)=i=1ndig(d)f(di)=d=1ng(d)i=1dnf(i)=d=1ng(d)S(dn)接着,将和式的第一项提出来,得到
g ( 1 ) ⋅ S ( n ) = ∑ i = 1 n h ( i ) − ∑ d = 2 n g ( d ) ⋅ S ( ⌊ n d ⌋ ) g(1)\cdot S(n)=\sum_{i=1}^n h(i) - \sum_{d=2}^n g(d)\cdot S(\left\lfloor \frac{n}{d} \right\rfloor) g(1)S(n)=i=1nh(i)d=2ng(d)S(dn)这样,如果 h ( i ) h(i) h(i)前缀和能很快地求出,对后面的式子利用分块(不过 g ( d ) g(d) g(d)也要能快速求和,且 d d d从2开始,不是从1开始),可以将求 S ( n ) S(n) S(n)的复杂度降到 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32)。杜教筛的套路就是找到一个 g g g,使得 f ∗ g = h f*g=h fg=h的前缀和能简单求出,顺便 g g g的求和也简单。注意,杜教筛不像线性筛,比如线性筛筛 φ 、 μ \varphi、\mu φμ这些函数一遍筛就把 [ 1 , n ] [1,n] [1,n]的都筛出来,而杜教筛是只筛出 S ( n ) S(n) S(n),不过从上面的式子很容易就想到递归求解的方式,可以把递归时求出的一些 S ( ⌊ n d ⌋ ) S(\left\lfloor\dfrac{n}{d}\right\rfloor) S(dn)保存下来,这样可以筛得快一些(因为数组没法开到1e9那种级别,可以用个map来存筛出来的 S S S)。一般前几项(比如说前1e6项)直接按照线性筛的筛法筛出来(积性函数有个性质——能被 O ( n ) O(n) O(n)筛出来),然后 O ( n ) O(n) O(n)求一遍前缀和来算;后面的项用上面推导出来的式子,拿杜教筛筛

来点例子:

  • S ( n ) = ∑ i = 1 n μ ( i ) S(n)=\sum\limits_{i=1}^n \mu(i) S(n)=i=1nμ(i)
    这里的 f = μ f=\mu f=μ,现在的目标是找一个 g g g,使得 f f f g g g得到的函数很好求前缀和,顺便 g g g也要比较好求和,从上文我们已经得到一个结论: μ ∗ I = ϵ \mu*I=\epsilon μI=ϵ,而 ϵ \epsilon ϵ的前缀和很好求,就是1,所以有
    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)

  • S ( n ) = ∑ i = 1 n φ ( i ) S(n)=\sum\limits_{i=1}^{n}\varphi(i) S(n)=i=1nφ(i)
    由欧拉函数的性质,我们知道 ( φ ∗ I ) ( n ) = n (\varphi*I)(n)=n (φI)(n)=n,也就是 φ ∗ I = i d \varphi*I=id φI=id i d id id求和很简单,所以有
    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)

  • S ( n ) = ∑ i = 1 n i ⋅ φ ( i ) S(n)=\sum\limits_{i=1}^n i\cdot \varphi(i) S(n)=i=1niφ(i)
    这个式子乍一看难找 g g g来和 f f f卷,这时候我们就回到狄利克雷卷积的定义,我们要找的是 h = f ∗ g h=f*g h=fg使得 h h h的前缀和比较好求,而 ( f ∗ g ) ( n ) = ∑ d ∣ n ( d ⋅ φ ( d ) ) ⋅ g ( n d ) (f*g)(n) = \sum\limits_{d|n}(d\cdot \varphi(d))\cdot g(\dfrac{n}{d}) (fg)(n)=dn(dφ(d))g(dn)。现在的问题就是前面的 d d d在这不好求,如果用 g g g能约掉就好了,我们发现 g ( n d ) g(\dfrac{n}{d}) g(dn)的分母有个 d d d,那么令 g = i d g=id g=id,那么 h ( n ) = ∑ d ∣ n φ ( d ) ⋅ n = n ⋅ ∑ d ∣ n φ ( d ) = n 2 h(n)=\sum\limits_{d|n}\varphi(d)\cdot n=n\cdot\sum\limits_{d|n} \varphi(d)=n^2 h(n)=dnφ(d)n=ndnφ(d)=n2,所以有
    S ( n ) = ∑ i = 1 n i 2 − ∑ d = 2 n d ⋅ S ( ⌊ n d ⌋ ) S(n)=\sum_{i=1}^n i^2-\sum_{d=2}^nd\cdot S(\left\lfloor \frac{n}{d} \right\rfloor) S(n)=i=1ni2d=2ndS(dn) 再根据 ∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 6 \sum\limits_{i=1}^n i^2=\dfrac{n(n+1)(2n+1)}{6} i=1ni2=6n(n+1)(2n+1),就能筛了

板子

  • 洛谷P4213 【模板】杜教筛(Sum)

    #include <cstdio>
    #include <map>
    #define N 5000010
    typedef long long ll;
    
    bool check[N];
    int mu[N], sum_mu[N], phi[N], prime[N >> 2], tot;
    ll sum_phi[N];
    std::map<int, int> map_mu;
    std::map<int, ll> map_phi;
    
    void get(int n) {
    	phi[1] = mu[1]= 1; check[1] = true;
    	for(int i = 2; i <= n; i++) {
            if(!check[i]) prime[++tot] = i, mu[i] = -1, phi[i] = i - 1;
            for(int j = 1; j <= tot && i * prime[j] <= n; j++) {
                check[i * prime[j]] = true;
                if(i % prime[j] == 0) {
                    mu[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 <= n; i++)
            sum_mu[i] = sum_mu[i - 1] + mu[i], sum_phi[i] = sum_phi[i - 1] + phi[i];
    }
    
    int djmu(int n) {
        if(n < N) return sum_mu[n];
        if(map_mu.count(n)) return map_mu[n];
        int ans = 1;
        for(int l = 2, r = 0; l <= n; l = r + 1) {
            r = n / (n / l);
            ans -= (r - l + 1) * djmu(n / l);
        }
        map_mu.insert(std::make_pair(n, ans));
        return ans;
    }
    
    ll djphi(int n) {
        if(n < N) return sum_phi[n];
        if(map_phi.count(n)) return map_phi[n];
        ll ans = (ll)(n + 1) * n >> 1;
        for(int l = 2, r = 0; l <= n; l = r + 1) {
            r = n / (n / l);
            ans -= (ll)(r - l + 1) * djphi(n / l);
        }
        map_phi.insert(std::make_pair(n, ans));
        return ans;
    }
    
    int main() {
        get(N - 1);
        int t;
        scanf("%d", &t);
        while(t--) {
            int n;
            scanf("%d", &n);
            printf("%lld %d\n", djphi(n), djmu(n));
        }
        return 0;
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值