• 杜教筛和狄利克雷卷积


    零、前置知识

    1. 积性函数

    积性函数的定义:若 ( a , b ) = 1 (a,b)=1 (a,b)=1,则 f ( a ⋅ b ) = f ( a ) ⋅ f ( b ) f(a\cdot b) = f(a) \cdot f(b) f(ab)=f(a)f(b)

    常见的积性函数有: φ \varphi φ 函数, μ \mu μ
    函数等。

    积性函数有以下性质:

    f ( x ) , g ( x ) f(x),g(x) f(x),g(x) 均为积性函数,则 h ( x ) = f ( x ) ⋅ g ( x ) h(x)=f(x)\cdot g(x) h(x)=f(x)g(x) 也为积性函数。

    一、介绍

    1.狄利克雷卷积

    定义 f ∗ g ( n ) f*g(n) fg(n) (读作: f f f g g g n n n 为大小)为: ∑ i ∣ n f ( i ) ⋅ g ( n i ) \sum\limits_{i | n} f(i)\cdot g\left(\dfrac{n}{i}\right) inf(i)g(in)

    狄利克雷卷积有着交换律、结合律,请读者自证。

    下面是一些常见函数:

    φ , μ , i d , I , ε \varphi,\mu,id,I,\varepsilon φ,μ,id,I,ε

    其中 i d ( n ) = n id(n)=n id(n)=n I ( n ) = 1 I(n)=1 I(n)=1 ε ( n ) \varepsilon(n) ε(n) n = 1 n=1 n=1 时为 1 1 1,否则为 0 0 0

    现在来计算一下: φ ∗ I ( n ) \varphi*I(n) φI(n)

    下面为计算过程:

    φ ∗ I ( n ) = ∑ i ∣ n φ ( i ) = n = i d

    φI(n)=i|nφ(i)=n=id" role="presentation" style="position: relative;">φI(n)=i|nφ(i)=n=id
    ===φI(n)inφ(i)nid

    还有一个常见的卷积: μ ∗ I ( n ) = ε \mu*I(n)=\varepsilon μI(n)=ε

    下面为证明:

    n = 1 n=1 n=1 时,显然成立

    不妨设: n = α 1 p 1 ⋅ α 2 p 2 ⋯ ⋅ α k p k n=\alpha_1^{p_1}\cdot \alpha_2^{p_2}\cdots \cdot \alpha_k^{p_k} n=α1p1α2p2αkpk

    L H S = ∑ i ∣ n μ ( i ) LHS=\sum\limits_{i|n}\mu(i) LHS=inμ(i)

    由于 μ ( i ) \mu(i) μ(i) 只有在不包含平方因子的时候才不是 0 0 0,所以:

    L H S = ( n 0 ) − ( n 1 ) + ( n 2 ) − ⋯ = ∑ i = 0 k ( − 1 ) i ⋅ 1 k − i ⋅ ( n i ) = 0

    LHS=(n0)(n1)+(n2)=i=0k(1)i1ki(ni)=0" role="presentation" style="position: relative;">LHS=(n0)(n1)+(n2)=i=0k(1)i1ki(ni)=0
    ===LHS(0n)(1n)+(2n)i=0k(1)i1ki(in)0

    ε \varepsilon ε 的定义正好符合。

    证毕。

    相信你已熟知狄利克雷卷积,接下来我们进入杜教筛部分。

    2.杜教筛

    杜教筛是在高效的复杂度内求出一个积性函数的前缀和。

    不妨设该函数为 f ( n ) f(n) f(n),其前缀和为 S ( n ) S(n) S(n),我们构造了另一个积性函数 g ( n ) g(n) g(n)

    接下来我们推一个式子:

    ∑ i = 1 n g ∗ f ( i ) = ∑ i = 1 n ∑ j ∣ i g ( i ) ⋅ f ( n i ) = ∑ j = 1 n ∑ i = 1 ⌊ n j ⌋ g [ j ] ⋅ f ( i ) = ∑ i = 1 n g ( i ) ⋅ S ( ⌊ n i ⌋ )

    i=1ngf(i)=i=1nj|ig(i)f(ni)=j=1ni=1njg[j]f(i)=i=1ng(i)S(ni)" role="presentation" style="position: relative;">i=1ngf(i)=i=1nj|ig(i)f(ni)=j=1ni=1njg[j]f(i)=i=1ng(i)S(ni)
    ===i=1ngf(i)i=1njig(i)f(in)j=1ni=1jng[j]f(i)i=1ng(i)S(⌊in⌋)

    接下来考虑 g ( 1 ) ⋅ S ( n ) g(1)\cdot S(n) g(1)S(n),即得到:

    g ( 1 ) ⋅ S ( n ) = ∑ i = 1 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) − ∑ i = 2 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) = ∑ i = 1 n g ∗ f ( i ) − ∑ i = 2 n g ( i ) ⋅ S ( ⌊ n i ⌋ )

    g(1)S(n)=i=1ng(i)S(ni)i=2ng(i)S(ni)=i=1ngf(i)i=2ng(i)S(ni)" role="presentation" style="position: relative;">g(1)S(n)=i=1ng(i)S(ni)i=2ng(i)S(ni)=i=1ngf(i)i=2ng(i)S(ni)
    ==g(1)S(n)i=1ng(i)S(⌊in⌋)i=2ng(i)S(⌊in⌋)i=1ngf(i)i=2ng(i)S(⌊in⌋)

    所以,如果我们构造的 g g g 满足如下条件:

    • 可以快速得到 g ( n ) g(n) g(n)
    • 可以快速得到 g ∗ f g*f gf 的前缀和

    然后注意到后面所减去的 S ( ⌊ n i ⌋ ) S(\lfloor\frac{n}{i}\rfloor) S(⌊in⌋) 是可以数论分块的,可以直接递归求得,这样复杂度为 n 3 4 n^{\frac{3}{4}} n43

    考虑到当我们要求的 S ( x ) S(x) S(x) 小于阙值时,可以通过线性筛预处理出,大于等于阙值时,通过上述方式递归即可。

    当阙值取 n 2 3 n^\frac{2}{3} n32 时,复杂度最优,为 n 2 3 n^\frac{2}{3} n32

    二、习题

    1.模板杜教筛

    本题即求 φ \varphi φ μ \mu μ 的前缀和。

    下面介绍求 φ \varphi φ 的前缀和, μ \mu μ 同理,此处不分析。

    我们取 g ( n ) = I ( n ) g(n)=I(n) g(n)=I(n),上面已证: φ ∗ I = i d \varphi*I=id φI=id

    所以,可得:

    S ( n ) = ∑ i = 1 n g ∗ f ( i ) − ∑ i = 2 n g ( i ) ⋅ S ( ⌊ n i ⌋ ) = ∑ i = 1 n i − ∑ i = 2 n S ( ⌊ n i ⌋ ) = n ⋅ ( n + 1 ) 2 − ∑ i = 2 n S ( ⌊ n i ⌋ )

    S(n)=i=1ngf(i)i=2ng(i)S(ni)=i=1nii=2nS(ni)=n(n+1)2i=2nS(ni)" role="presentation" style="position: relative;">S(n)=i=1ngf(i)i=2ng(i)S(ni)=i=1nii=2nS(ni)=n(n+1)2i=2nS(ni)
    ===S(n)i=1ngf(i)i=2ng(i)S(⌊in⌋)i=1nii=2nS(⌊in⌋)2n(n+1)i=2nS(⌊in⌋)

    AC code

    #include 
    using namespace std;
    #define int long long
    const int N = 3000300;
    int T, n, B;
    int a[N], cnt;
    bool b[N];
    int mu[N], phi[N], summu[N], sumphi[N];
    unordered_map<int, int> smu, sphi;
    void Get_Prime(int n) {
        phi[1] = 1;
        mu[1] = 1;
        for (int i = 2; i <= n; ++i) {
            if (!b[i]) {
                a[++cnt] = i;
                phi[i] = i - 1;
                mu[i] = -1;
            }
            for (int j = 1; j <= cnt && a[j] * i <= n; ++j) {
                b[a[j] * i] = 1;
                phi[a[j] * i] = phi[i] * (a[j] - 1);
                mu[a[j] * i] = -mu[i];
                if (i % a[j] == 0) {
                    phi[a[j] * i] = phi[i] * a[j];
                    mu[a[j] * i] = 0;
                    break;
                }
            }
        }
        for (int i = 1; i <= n; ++i)
            summu[i] = summu[i - 1] + mu[i], sumphi[i] = sumphi[i - 1] + phi[i];
        return;
    }
    int GetSphi(int n) {
        if (n <= B)
            return sumphi[n];
        if (sphi.count(n) > 0)
            return sphi[n];
        int ans = n * (n + 1) / 2;
        for (int l = 2, r; l <= n;) {
            int val = n / l;
            r = n / val;
            ans -= (r - l + 1) * GetSphi(val);
            l = r + 1;
        }
        sphi[n] = ans;
        return ans;
    }
    int GetSmu(int n) {
        if (n <= B)
            return summu[n];
        if (smu.count(n) > 0)
            return smu[n];
        int ans = 1;
        for (int l = 2, r; l <= n;) {
            int val = n / l;
            r = n / val;
            ans -= (r - l + 1) * GetSmu(val);
            l = r + 1;
        }
        smu[n] = ans;
        return ans;
    }
    signed main() {
        scanf("%lld", &T);
        Get_Prime(N - 1);
        for (int __ = 1; __ <= T; ++__) {
            scanf("%lld", &n);
            B = pow(n, 2.0 / 3);
            printf("%lld %lld\n", GetSphi(n), GetSmu(n));
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    2.四元组计数

    题意

    给定一个 n n n,求出有多少个整数 a , b , c , d ∈ [ 1 , n ] a,b,c,d \in [1,n] a,b,c,d[1,n],满足: a ⋅ b = c ⋅ d a\cdot b = c\cdot d ab=cd

    数据范围: 1 ≤ n ≤ 1 0 11 1 \le n \le 10^{11} 1n1011

    思路

    注意到原式等价于: a c = b d \frac{a}{c}=\frac{b}{d} ca=db,考虑将这一类分数约分后归为一类: p q ( p , q ) = 1 \frac{p}{q} (p,q)=1 qp(p,q)=1。那么方案数:

    ∑ p = 1 n ∑ q = 1 n [ ( p , q ) = 1 ] ⋅ n max ⁡ ( p , q ) 2 = 2 ⋅ ∑ p = 1 n ∑ q = 1 p [ ( p , q ) = 1 ] ⋅ ⌊ n p ⌋ 2 − n 2 = 2 ⋅ ∑ p = 1 n ⌊ n p ⌋ 2 ⋅ φ ( p ) − n 2

    p=1nq=1n[(p,q)=1]nmax(p,q)2=2p=1nq=1p[(p,q)=1]np2n2=2p=1nnp2φ(p)n2" role="presentation" style="position: relative;">p=1nq=1n[(p,q)=1]nmax(p,q)2=2p=1nq=1p[(p,q)=1]np2n2=2p=1nnp2φ(p)n2
    ==p=1nq=1n[(p,q)=1]max(p,q)n22p=1nq=1p[(p,q)=1]pn2n22p=1npn2φ(p)n2

    我们记 k = ∑ p = 1 n ⌊ n p ⌋ 2 ⋅ φ ( p ) k=\sum_{p=1}^n\left\lfloor\frac{n}{p}\right\rfloor^2\cdot \varphi(p) k=p=1npn2φ(p),则答案为 2 ⋅ k − n 2 2\cdot k-n^2 2kn2

    k = ∑ i = 1 n φ ( i ) ⋅ ⌊ n i ⌋ 2 = ∑ i = 1 n φ ( i ) ∑ j = 1 ⌊ n i ⌋ ( 2 j − 1 ) = 2 ⋅ ∑ i ⋅ j ≤ n φ ( i ) ⋅ j − ∑ i ⋅ j ≤ n φ ( i ) = 2 ⋅ ∑ T = 1 n ∑ d ∣ T φ ( d ) ⋅ T d − ∑ T = 1 n ∑ d ∣ T φ ( d ) = 2 ⋅ ∑ i = 1 n φ ∗ i d ( i ) − ∑ i = 1 n i

    k=i=1nφ(i)ni2=i=1nφ(i)j=1ni(2j1)=2ijnφ(i)jijnφ(i)=2T=1nd|Tφ(d)TdT=1nd|Tφ(d)=2i=1nφid(i)i=1ni" role="presentation" style="position: relative;">k=i=1nφ(i)ni2=i=1nφ(i)j=1ni(2j1)=2ijnφ(i)jijnφ(i)=2T=1nd|Tφ(d)TdT=1nd|Tφ(d)=2i=1nφid(i)i=1ni
    k=====i=1nφ(i)in2i=1nφ(i)j=1in(2j1)2ijnφ(i)jijnφ(i)2T=1ndTφ(d)dTT=1ndTφ(d)2i=1nφid(i)i=1ni

    现在已经很明朗了。

    我们再记 a n s = ∑ i = 1 n φ ∗ i d ( i ) ans=\sum_{i=1}^n\varphi*id(i) ans=i=1nφid(i),在 k = 2 ⋅ a n s − n ∗ ( n + 1 ) 2 k=2\cdot ans - \frac{n*(n+1)}{2} k=2ans2n(n+1)

    那么 a n s ans ans 也就是积性函数 f ( n ) = φ ∗ i d ( n ) f(n)=\varphi*id(n) f(n)=φid(n) 的前缀和。

    我们考虑给他卷上一个 I I I,注意到狄利克雷卷积具有交换律,所以其等价于 φ ∗ I ∗ i d ( n ) \varphi*I*id(n) φIid(n),即 i d ∗ i d ( n ) = ∑ d ∣ n d ⋅ n d = n id*id(n)=\sum_{d|n} d\cdot \frac{n}{d}=n idid(n)=dnddn=n,所以它的值就是 n ⋅ p ( n ) n\cdot p(n) np(n) p ( n ) p(n) p(n) n n n 的约数个数。考虑如何快速求出 i d ∗ i d ( n ) id*id(n) idid(n),继续推式子。

    ∑ i = 1 n i d ∗ i d ( i ) = ∑ i = 1 n ∑ j = 1 ⌊ n i ⌋ i ⋅ j = ∑ i = 1 n i ⌊ n i ⌋ ⋅ ( ⌊ n i ⌋ + 1 ) 2

    i=1nidid(i)=i=1nj=1niij=i=1nini(ni+1)2" role="presentation" style="position: relative;">i=1nidid(i)=i=1nj=1niij=i=1nini(ni+1)2
    ==i=1nidid(i)i=1nj=1iniji=1ni2in(⌊in+1)

    这个可以通过整除分块,根号复杂度内求得,剩下的就是直接杜教筛即可。

    式子:

    s u m n = ∑ i = 1 n i d ∗ i d ( i ) − ∑ i = 2 n s u m n / i sum_n=\sum_{i=1}^nid*id(i)-\sum_{i=2}^nsum_n/i sumn=i=1nidid(i)i=2nsumn/i

    前半部分和后半部分皆用整除分块,复杂度还是 θ ( n 2 3 ) \theta(n^\frac{2}{3}) θ(n32)

    Warning:此题数据范围有点大,需要 __int128

    AC code

    #include 
    using namespace std;
    #define int __int128
    const int N = 4000010, M = 400010;
    int T, B, n;
    int a[M], cnt;
    int v[N];
    int pwr[N];
    int tim[N];
    int pid[N];
    map<int, int> mp;
    void Get_Prime(int n) {
        for (int i = 1; i <= n; ++i)
            v[i] = i;
        pid[1] = 1;
        for (int i = 2; i <= n; ++i) {
            if (v[i] == i) {
                a[++cnt] = i;
                pwr[i] = 1;
                tim[i] = i;
                pid[i] = 2 * i - 1;
            }
            for (int j = 1; j <= cnt && a[j] * i <= n; ++j) {
                v[a[j] * i] = a[j];
                pwr[a[j] * i] = 1;
                tim[a[j] * i] = a[j];
                pid[a[j] * i] = pid[a[j]] * pid[i];
                if (i % a[j] == 0) {
                    tim[a[j] * i] = tim[i] * a[j];
                    pwr[a[j] * i] = pwr[i] + 1;
                    int val = tim[a[j] * i], pval = val + pwr[a[j] * i] * (v[a[j] * i] - 1) * val / v[a[j] * i];
                    pid[a[j] * i] = pid[a[j] * i / val] * pval;
                    break;
                }
            }
        }
        for (int i = 1; i <= n; ++i)
            pid[i] += pid[i - 1];
        return;
    }
    int ask(int n) {
        if (n <= B)
            return pid[n];
        if (mp.count(n) > 0)
            return mp[n];
        int ans = 0;
        for (int l = 1, r; l <= n;) {
            int val = n / l;
            r = n / val;
            ans += (r - l + 1) * (l + r) / 2 * val * (val + 1) / 2;
            l = r + 1;
        }
        for (int l = 2, r; l <= n;) {
            int val = n / l;
            r = n / val;
            ans -= (r - l + 1) * ask(val);
            l = r + 1;
        }
        mp[n] = ans;
        return ans;
    }
    __int128 readin() {
        __int128 x = 0, f = 1;
        char ch = getchar();
        while (ch < '0' || ch > '9') {
            if (ch == '-')
                f = -1;
            ch = getchar();
        }
        while (ch >= '0' && ch <= '9') {
            x = x * 10 + (ch ^ 48);
            ch = getchar();
        }
        return x * f;
    }
    void print(__int128 x) {
        if (x < 0)
            puts("-"), x = -x;
        if (!x) {
            return;
        }
        print(x / 10);
        putchar(x % 10 + '0');
        return;
    }
    signed main() {
        Get_Prime(N - 1);
        B = N - 1;
        T = readin();
        for (int __ = 1; __ <= T; ++__) {
            n = readin();
            int ans = ask(n);
            ans = ans * 2 - n * (n + 1) / 2;
            ans = ans * 2 - n * n;
            print(ans);
            puts("");
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
  • 相关阅读:
    JavaScript Promise
    微擎模块 文明城市小程序1.0.5 文明城市+智慧工商+智慧城管随手拍
    Java基础 引用数据类型String(字符串)
    B树和B+树的介绍和对比,以及MySQL为何选择B+树
    java计算机毕业设计济南旅游网站MyBatis+系统+LW文档+源码+调试部署
    物联网应用-分布式对象储存工具-MinIO 对象存储win部署及使用
    大数据安全技术总体视图
    Machine Learning(study notes)
    C#将图片转换为ICON格式(程序运行图标)
    电力电子变换器的科研创新思路(二)
  • 原文地址:https://blog.csdn.net/yyf525/article/details/133419882