杜教筛

简介

杜教筛是一个用于处理数论函数前缀和的方法,优化后它可以在 \(O(n^{\frac 2 3})\) 的时间复杂度下求解一个前缀和

原理

如果给定一个函数\(f\) ,要求计算 \(S(n)=\sum_{i=1}^nf(i)\) 。为了降低复杂度,我们想办法构造一个 \(S(n)\) 关于 \(S(\lfloor\frac{n}{i}\rfloor)\) 的递推式

对于任意一个数论函数 \(g\) ,都满足:

\[\sum_{i=1}^n (f \ast g)(i)=\sum_{i=1}^n g(i)S(\lfloor\frac{n}{i}\rfloor) \]

证明:

\[\begin{aligned} &\sum_{i=1}^n\sum_{d\mid i} g(d)f(\frac i d)\\ =&\sum_{i=1}^n \sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}g(i)f(j)\\ =&\sum_{i=1}^n g(i)\sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}f(j)\\ =&\sum_{i=1}^n g(i)S(\lfloor\frac{n}{i}\rfloor) \end{aligned} \]

第一个等号的式子中的 \(i,j\) 分别枚举的是原式中的 \(d,\frac{i}{d}\)

所以可以得到:

\[g(1)S(n)=\sum_{i=1}^n (f \ast g)(i)-\sum_{i=2}^n g(i)S(\lfloor\frac{n}{i}\rfloor) \]

如果 \((f\ast g)(i)\) 的前缀和与 \(g(i)\) 的区间和都能以 \(O(1)\) 计算,那么我们就能较快求解 \(S(n)\)

时间复杂度

\((f\ast g)(i)\)\(g(i)\) 的前缀和都能以 \(O(1)\) 求出时,复杂度为 \(O(n^{\frac 3 4})\) ,如果用线性筛预处理出前 \(n^{\frac 2 3}\) 前缀和,复杂度可以优化到 \(O(n^{\frac 2 3})\) ,此外,还可以用哈希表对大于 \(n^{\frac 2 3}\) 的数也进行记忆化

复杂度证明:参考 杜教筛时间复杂度证明 - IcMtr

模板

Luogu P4213

\(\varphi(x),\mu(x)\) 的前缀和

  • 对于 \(\varphi(x)\) ,可以设 \(g(x)=1\) ,因为 \(\varphi \ast 1=\operatorname{id}\)

  • 对于 \(\mu(x)\) ,可以设 \(g(x)=1\) ,因为 \(\mu \ast 1=\epsilon\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int MAX_23 = 2000000;

ll phi[MAX_23 + 5], mu[MAX_23 + 5];
unordered_map<int, int> s_mu;
unordered_map<int, ll> s_phi;

int cnt;
bool is_prime[MAX_23 + 5];
int prime[MAX_23 + 5];

void sieve(int n)
{
    cnt = 0;
    memset(is_prime, 1, sizeof(is_prime));
    is_prime[1] = false;
    mu[1] = phi[1] = 1;
    for(int i = 2; i <= n; i++) {
        if(is_prime[i]) {
            prime[++cnt] = i;
            mu[i] = -1;
            phi[i] = i - 1;
        }
        for(int j = 1; j <= cnt && i * prime[j] <= n; j++) {
            int t = i * prime[j];
            is_prime[t] = false;
            if(i % prime[j]) {
                phi[t] = phi[i] * phi[prime[j]];
                mu[t] = -mu[i];
            } else {
                phi[t] = phi[i] * prime[j];
                mu[t] = 0;
                break;
            }
        }
    }
    for(int i = 1; i <= n; i++) {
        mu[i] = mu[i - 1] + mu[i];
        phi[i] = phi[i - 1] + phi[i];
    }
}

ll sum_phi(int n)
{
    if(n <= MAX_23)
        return phi[n];
    if(s_phi[n])
        return s_phi[n];
    ll res = ((1ll + n) * n) / 2ll;
    for(unsigned int l = 2, r; l <= n; l = r + 1) {
        r = n / (n / l);
        res -= (r - l + 1) * sum_phi(n / l);
    }
    s_phi[n] = res;
    return res;
}

int sum_mu(int n)
{
    if(n <= MAX_23)
        return mu[n];
    if(s_mu[n])
        return s_mu[n];
    int res = 1;
    for(int l = 2, r; l <= n; l = r + 1) {
        r = n / (n / l);
        res -= 1ll * (r - l + 1) * sum_mu(n / l);
    }
    s_mu[n] = res;
    return res;
}

int main()
{
    sieve(MAX_23);
    int T, n;
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        printf("%lld %d\n", sum_phi(n), sum_mu(n));
    }
    return 0;
}
posted @ 2022-03-01 11:43  f(k(t))  阅读(47)  评论(0编辑  收藏  举报