嘘~ 正在从服务器偷取页面 . . .

洛谷P4213 【模板】杜教筛 题解


洛谷P4213 【模板】杜教筛 题解

题目链接:P4213 【模板】杜教筛

题意

给定一个正整数,求

\[ \mathrm{ans}_1=\sum_{i=1}^n\varphi(i) \\[4pt]\mathrm{ans}_2=\sum_{i=1}^n \mu(i) \] 输入格式

本题有多组数据。

输入的第一行为一个整数,表示数据组数 \(T\)

接下来 \(T\) 行,每行一个整数 \(n\),表示一组询问。

输出格式

对于每组询问,输出一行两个整数,分别代表 \(\mathrm{ans}_1\)\(\mathrm{ans}_2\)

数据范围

对于全部的测试点,保证 \(1 \leq T \leq 10\)\(1 \leq n \lt 2^{31}\)

杜教筛是一种能在非线性时间内求解积性函数的前缀和的算法。

\(S(n) = \sum_{i=1}^{n} f(i)\) ,其中 \(f\) 为积性函数。另设积性函数 \(g\) ,考虑他们的狄利克雷卷积的前缀和 \[ \begin{aligned} \sum_{i = 1}^n (f * g) (i) &=\sum_{i=1}^n \sum_{d\mid i} f(d) \left(\frac{i}{d}\right) \\[6pt]&=\sum_{d=1}^n g(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i) \\[6pt]&=\sum_{d=1}^ng(d)S\left(\left\lfloor\frac{n}{d}\right\rfloor\right) \end{aligned} \] 考察 \(g(1)S(n)\) ,可知 \[ g(1)S(n) = \sum_{i=1}^n g(i) S\left(\left\lfloor\frac{n}{i}\right\rfloor\right)-\sum_{i=2}^n g(i) S\left(\left\lfloor\frac{n}{i}\right\rfloor\right) \] 由前面的式子可得 \[ g(1) S(n)=\sum_{i=1}^n(f * g)(i)-\sum_{i=2}^n g(i) S\left(\left\lfloor\frac{n}{i}\right\rfloor\right) \] 于是只要找到一个合适的积性函数 \(g\) ,就可以快速计算 \(\sum_{i=1}^n(f * g)(i)\)\(g\) 的前缀和

这样就可以用数论分块递归地求解(递归 \(S\) )。根据狄利克雷卷积的性质,可知 \[ \varphi * \mathbf{1}= \rm{id} \\[6pt] \mu * \mathbf{1}= \epsilon \] 于是求 \(\varphi\) 的前缀和可以设 \(f=\varphi,\,g = \mathbf{1}\) ,则 \(f * g = \mathrm{id}\) ,其前缀和为 \(\frac{n(n+1)}{2}\)

同理对于 \(\mu\) 的前缀和,可设 \(f=\mu,\,g=\mathbf{1}\) ,则 \(f*g = \epsilon\) ,其前缀和为 \(1\)

那么杜教筛的基本流程如下

ll GetSum(int n) // 算 f 前缀和的函数
{
    ll ans = f_g_sum(n); // 算 f * g 的前缀和
    for(ll l = 2, r; l <= n; l = r + 1) // 注意从 2 开始数论分块
    {
        r = (n / (n / l)); 
        ans -= (g_sum(r) - g_sum(l - 1)) * GetSum(n / l); // g_sum 是 g 的前缀和
    } return ans; 
}

这个代码的复杂度是 \(\mathcal{O}(n^{\frac{3}{4}})\) 的,并非最优方案,不过我们先来证明一下。

证明 设复杂度为 \(T(n)\) ,要求出 \(S(n)\) 需要求出 \(\sqrt{n}\)\(S\left(\left\lfloor\frac{n}{i}\right\rfloor\right)\) 的值,那么 \[ T(n) = \mathcal{O}\left(\sum_{i=1}^{\sqrt{n}} \left(\sqrt{i}+\sqrt{\frac{n}{i}}\right)\right)=\mathcal{O}(n^{\frac{3}{4}}) \]

\(\square\)

上面代码的形式使得我们考虑记忆化,并先用线性筛求出前 \(m\) 个答案,则 \[ T(n)=\mathcal{O}\left(\sum_{i=1}^{\left\lfloor\frac{n}{m}\right\rfloor} \sqrt{\frac{n}{i}}\right) =\mathcal{O}\left(\frac{n}{\sqrt{m}}\right) \]\(m = n^{\frac{2}{3}}\) 时,\(T(n)\) 可以取到 \(\mathcal{O}(n^{\frac{2}{3}})\) 。并且显然 \(m\) 越大跑的越快。

另外可以想到用哈希表来记忆化,注意如果 \(x \le m\) 时就不要扔到哈希表里了,直接查数组即可。

时间复杂度 \(\mathcal{O}(Tn^{\frac{2}{3}})\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define rep(i, a, b) for(int i = (a), i##END = (b); i <= i##END; i++)
#define Rep(i, a, b) for(int i = (a), i##END = (b); i >= i##END; i--)
#define N ((int)(5e6 + 15))

char ck[N]; int pcnt, p[N]; int phi[N], mu[N];
unordered_map<int, int> sum_phi, sum_mu;
void init(const int _ = N - 5)
{
    phi[1] = mu[1] = 1;
    rep(i, 2, _)
    {
        if(!ck[i]) { p[++pcnt] = i; phi[i] = i - 1; mu[i] = -1; }
        for(int j = 1; j <= pcnt && i * p[j] <= _; j++)
        {
            const int pos = i * p[j]; ck[pos] = true;
            if(i % p[j] == 0) { phi[pos] = phi[i] * p[j]; break; }
            else { phi[pos] = phi[i] * phi[p[j]]; mu[pos] = -mu[i]; }
        }
    }
    rep(i, 1, _) { phi[i] += phi[i - 1]; mu[i] += mu[i - 1]; }
}
int get_sum_phi(int n)
{
    if(n <= N - 5) return phi[n];
    if(sum_phi[n]) return sum_phi[n];
    int 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) * get_sum_phi(n / l);
    }
    return sum_phi[n] = res;
}
int get_sum_mu(int n)
{
    if(n <= N - 5) return mu[n];
    if(sum_mu[n]) return sum_mu[n];
    int res = 1ll;
    for(int l = 2, r; l <= n; l = r + 1)
    {
        r = n / (n / l);
        res -= 1ll * (r - l + 1) * get_sum_mu(n / l);
    }
    return sum_mu[n] = res;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int qwq; cin >> qwq; init();
    while(qwq--) { 
        static int x; cin >> x;
        cout << get_sum_phi(x) << ' ' << get_sum_mu(x) << '\n';
    }
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/tojxbb76


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录