Luogu 4213 【模板】杜教筛(Sum)

当作杜教筛的笔记吧。

杜教筛

要求一个积性函数$f(i)$的前缀和,现在这个东西并不是很好算,那么我们考虑让它卷上另外一个积性函数$g(i)$,使$(f * g)$的前缀和变得方便计算,然后再反推出这个$f$函数的前缀和。

$$\sum_{i = 1}^{n}(f * g)(i) = \sum_{i = 1}^{n}\sum_{d | i}g(d)f(\frac{i}{d}) = \sum_{d = 1}^{n}g(d)\sum_{i = 1}^{\left \lfloor \frac{n}{d} \right \rfloor}f(i) = \sum_{d = 1}^{n}g(d)S(\frac{n}{d}{})$$

把$g(1)S(n)$移过来

$$g(1)S(n) = \sum_{i = 1}^{n}(f * g)(i) - \sum_{i = 2}^{n}g(i)S(\left \lfloor \frac{n}{i} \right \rfloor)$$

这个式子就是杜教筛的精髓了。

我们可以先筛出$[1, \sqrt{n}]$区间内的该积性函数的前缀和,然后再分块递归求解$(\sqrt{n}, n]$中的该函数的前缀和,可以做到$O(n^{\frac{2}{3}})$的优秀的复杂度(并不会这个复杂度的证明)。

应该用一个哈希表存一下已经计算过的各个$S(n)$的值($unordered\_map$)。

接下来的问题就是考虑如何搭配出这个积性函数$g$了。

模板题

考虑如何计算$\mu$和$\varphi$。

我们知道$\mu * I = \epsilon$,那么有

$$S(n) = \sum_{i = 1}^{n}\epsilon(i) - \sum_{i = 2}^{n}S(\left \lfloor \frac{n}{i} \right \rfloor)$$

滑稽吧,$\epsilon$的前缀和还不是$1$。

我们又知道$\varphi * I = id$,那么又有

$$S(n) = \sum_{i = 1}^{n}id(i) - \sum_{i = 2}^{n}S(\left \lfloor \frac{n}{i} \right \rfloor)$$

而$\sum_{i = 1}^{n}id(i) = \sum_{i = 1}^{n}i = \frac{i(i + 1)}{2}$。

解决了!

Code:

#include <cstdio>
#include <cstring>
#include <unordered_map>
using namespace std;
typedef long long ll;

const int N = 5e6 + 5;
const int Maxn = 5e6;

int testCase, pCnt = 0, pri[N], mu[N], phi[N];
ll sumMu[N], sumPhi[N];
bool np[N];
unordered_map <int, ll> sMu, sPhi;

template <typename T>
inline void read(T &X) {
    X = 0; char ch = 0; T op = 1;
    for (; ch > '9'|| ch < '0'; ch = getchar())
        if (ch == '-') op = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        X = (X << 3) + (X << 1) + ch - 48;
    X *= op;
}

inline void sieve() {
    mu[1] = 1, phi[1] = 1;
    for (int i = 2; i <= Maxn; i++) {
        if (!np[i]) pri[++pCnt] = i, phi[i] = i - 1, mu[i] = -1;
        for (int j = 1; j <= pCnt && i * pri[j] <= Maxn; j++) {
            np[i * pri[j]] = 1;
            if (i % pri[j] == 0) {
                phi[i * pri[j]] = phi[i] * pri[j];
                mu[i * pri[j]] = 0;
                break;
            }
            phi[i * pri[j]] = phi[i] * phi[pri[j]];
            mu[i * pri[j]] = -mu[i];
        }
    }
    
    for (int i = 1; i <= Maxn; i++) {
        sumMu[i] = sumMu[i - 1] + mu[i];
        sumPhi[i] = sumPhi[i - 1] + phi[i];
     }
}

ll getPhi(int n) {
    if (n <= Maxn) return sumPhi[n];
    if (sPhi[n]) return sPhi[n];
    ll res = 1LL * n * (n + 1) / 2;
    for (int l = 2, r; l <= n; l = r + 1) {
        r = (n / (n / l));
        res -= 1LL * (r - l + 1) * getPhi(n / l);
    }
    return sPhi[n] = res;
}

ll getMu(int n) {
    if (n <= Maxn) return sumMu[n];
    if (sMu[n]) return sMu[n];
    ll res = 1LL;
    for (int l = 2, r; l <= n; l = r + 1) {
        r = (n / (n / l));
        res -= 1LL * (r - l + 1) * getMu(n / l);
    }
    return sMu[n] = res;
}

int main() {
    sieve();
    read(testCase);
    for (int n; testCase--; ) {
        read(n);
        printf("%lld %lld\n", getPhi(n), getMu(n));
    }
    return 0;
}
View Code

感觉时限特别急,能别开$long \ long$就别开。

猜你喜欢

转载自www.cnblogs.com/CzxingcHen/p/10211296.html
今日推荐