杜教筛学习笔记

杜教筛

杜教筛的基本形式

对于积性函数g(n)我们希望求他的前缀和Sg(n),如果有另一积性函数f(n)满足fg=h,且fh的前缀和易求,那么我们可以通过Sf(n)Sh(n)快速的求出Sg(n)

Sh(n)=i=1nd|if(d)g(nd)=d=1ni=1ndf(d)g(i)=d=1nf(d)Sg(nd)Sg(n)=Sh(n)d=2nf(d)Sg(nd)

对后面的Sg(nd)进行数论分块就可以更快速的求出g的前缀和。

时间复杂度分析:

在求解过程中我们只需要关注Sg{nk|kN}处的取值,这部分一共只有O(n)种取值,因此整个时间复杂度是:

i=1nO(i)+i=1nO(ni)0n(x+nx)dx=O(n34)i=1nO(i)+i=1nO(ni)n2ini=O(n34)

通过上面的分析我们可以知道杜教筛的时间复杂度是O(n34)

如果能预处理出前n23项的前缀和,复杂度可以优化到O(n23)

一些例子:

  • μ的前缀和,有μI=e
    所以,Sμ(n)=1i=2nSμ(ni)

  • φ的前缀和,有φI=id
    所以,Sφ(n)=n(n+1)2i=2nSφ(ni)

贝尔级数

我们发现如果要用杜教筛求解积性函数前缀和问题,如何构造合适的狄利克雷卷积式子是很重要的一环,贝尔级数就是一个强有力的构造工具。

对于积性函数我们重点关注其在质数的幂处的取值,积性函数f(n)在质数p处的贝尔级数定义为Fp(x)=i=0f(pi)xi

然后数论函数的狄利克雷卷积就可以变成一般多项式的卷积:

h=fgHp(x)=i=0h(pi)xi=i=0k=0if(pk)xkg(pik)xik=Gp(x)Fp(x)

一些常见积性函数的贝尔级数:

  • e1
  • Ii=0xi=11x
  • idi=0pixi=11px
  • idki=0pkixi=11pkx
  • μi=0μ(pi)xi=1x
  • φ1+i=1pi1(p1)xi=1+p1pi=1pixi=1x1px

一些例子:

  • 求积性函数f(pk)=pk+[k>0](1)k的前缀和。

    fFp(x)=1+i=1(pi+(1)i)xi=1+px2(1px)(1+x)Fp(x)(1px)(1+x)=1+px2,(1px)μid()(1+x)μμ1+px2h(n)=[d,n=d2μ2(n)=1]ng(n)=(μμ)(μid),Sf(n)=Sh(n)d=2ng(d)Sf(nd)i=1nh(i)=i=1niμ2(i)g(n)(1px)(1+x)11px=1+xSg(n)=Sμμ(n)d=2nd(d+1)2Sg(nd)μμ(μμ)(n)=[n=1]=d|nμ(d)=d2|nμ(d)Sμμ(n)=i=1nd2|iμ(d)=d=1nμ(d)nd2

Powerful Number

PN的定义是不含有次数为1的质因子的正整数。即考虑正整数n的标准分解形式n=i=1kpiai,iai2

这种形式的正整数一定可以写成a2b3,所以我们可以大概估计一下PN的数量,在1nnx23dx=O(n)级别。这样的性质会给求一些积性函数的前缀和带来便利。

假设我们要求积性函数f(n)的前缀和,我们可以先构造一个新的积性函数g(n)满足pprime,f(p)=g(p)

此时必然存在一个积性函数h(n)满足f=gh对于pprime,f(p)=g(1)h(p)+g(p)h(1)=h(p)+g(p)

而根据g的构造我们可以得出pprime,h(p)=0也就是说h只在PN处对答案有贡献,而PN的数量只有O(n)!

Sf(n)=i=1nd|ig(d)h(id)=d=1nh(d)Sg(nd)=dPNh(d)Sg(nd)

接下来只需要求解h在PN处的取值,Sg可以用杜教筛计算。

考虑积性函数的取值只需要考虑其在质数的幂次处的取值:

f(pk)=i=0kg(pi)h(pki)h(pk)=f(pk)i=1kg(pi)h(pki)

由于是PN这里只需要考虑小于n的质数,这部分暴力计算即可。

例子:定义积性函数f(pk)=pk(pk1)pprimeSf(n)

构造g(n)=nφ(n)g(p)=p(p1)=f(p)pprime

写出g的贝尔级数1+i=1pipi1(p1)xi=1px1p2x

注意到gid=id2,所以Sg(n)=n(n+1)(2n+1)6d=2nn(n+1)2Sg(nd)

对于h的PN部分暴力计算。

code:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define int long long

using namespace std;

const int mo = 1e9 + 7;
const int inv2 = (mo + 1) >> 1;
const int inv3 = (mo + 1) / 3;
const int inv6 = 1ll * inv2 * inv3 % mo;

int prime[200005], cnt = 0, g[2000005], n, sum[100005], ans = 0;
bool a[2000005];
vector<int> h[10005];

int mi(int o, int p) {
    int yu = 1;
    while (p) {
        if (p & 1) yu = 1ll * yu * o % mo;
        o = 1ll * o * o % mo;
        p >>= 1;
    }
    return yu;
}

int S(int N) { return 1ll * N % mo * (N % mo + 1) % mo * inv2 % mo; }

int cal(int N) {
    if (N <= 2000000) return g[N];
    if (sum[n / N] != -1) return sum[n / N];
    int yu = 1ll * N % mo * (N % mo + 1) % mo * ((N + N) % mo + 1) % mo;
    yu = 1ll * yu * inv6 % mo;
    for (int l = 2, r; l <= N; l = r + 1) {
        r = N / (N / l);
        yu -= 1ll * (S(r) - S(l - 1)) * cal(N / l) % mo;
        yu = (yu % mo + mo) % mo;
    }
    return sum[n / N] = yu;
}

void dfs(int o, int p, int q) {
    ans = (ans + 1ll * q * cal(n / o) % mo) % mo;
    for (int tt = p; tt <= cnt; tt++) {
        if (n / o < prime[tt] * prime[tt]) break;
        int yu = o * prime[tt] * prime[tt];
        for (int t = 2; yu <= n; yu *= prime[tt], t++) {
            dfs(yu, tt + 1, 1ll * q * h[tt][t] % mo);
        }
    }
}

signed main() {
    memset(sum, -1, sizeof(sum));
    for (int i = 2; i <= 2000000; i++) {
        if (!a[i]) {
            prime[++cnt] = i;
            g[i] = 1ll * i * (i - 1) % mo;
        }
        for (int j = 1; j <= cnt && i * prime[j] <= 2000000; j++) {
            a[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                g[i * prime[j]] = 1ll * g[i] * prime[j] % mo * prime[j] % mo;
                break;
            }
            g[i * prime[j]] = 1ll * g[i] * prime[j] % mo * (prime[j] - 1) % mo;
        }
    }
    g[1] = 1;
    for (int i = 2; i <= 2000000; i++) {
        g[i] = (g[i - 1] + g[i]) % mo;
    }
    scanf("%lld", &n);
    for (int i = 1; i <= cnt; i++) {
        if (prime[i] > 100000) break;
        h[i].push_back(1);
        for (int j = prime[i], k = 1; j <= n; j *= prime[i], k++) {
            int yu = 1ll * j % mo * (j % mo - 1) % mo;
            for (int s = 0; s < k; s++) {
                yu -= 1ll * h[i][s] * mi(prime[i], 2 * k - 2 * s - 1) % mo *
                      (prime[i] - 1) % mo;
                yu = (yu % mo + mo) % mo;
            }
            h[i].push_back(yu);
        }
    }
    dfs(1, 1, 1);
    cout << ans << endl;
    return 0;
}
posted @   clapp  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示