杜教筛学习笔记

【前言】

杜教筛是一种利用递归思想求解数论函数的前缀和的高效思想。

优秀的复杂度和简单明快的思想使其在很多地方都有应用。

前置芝士部分可以完全参照莫比乌斯反演学习笔记中的狄利克雷卷积与数论函数部分。

【杜教筛】

假设有数论函数 \(f(i)\)

我们已经掌握了某种 \(O(n)\) 求它的方法,当然也会了 \(O(n)\)\(\sum_{i=1}^n f(i)\)(直接加起来就是了)。

但是 duliu 出题人决定将 \(n\) 的范围调整至 \(10^9\),此时还是要求出 \(f(i)\) 的前缀和,那么杜教筛就有用了。

这是我们需要开动想象力构造出卷积 \(f*g=h\),同时我们可以在不弱于杜教筛的时间内求出 \(g,h\) 的值。

那么此时就可以杜教筛出 \(f\) 的前缀和了。

有些抽象,用 \(\varphi\) 举个例子。

首先我们会用线性筛求 \(\varphi\) 及其前缀和。

同时我们知道 \(Id=\varphi*1\)

现在我们要求 \(\phi(n)=\sum_{i=1}^n \varphi(i)\)

那么通常的推到方式是将两边求和,显然等式任然成立,即:

\[\sum\limits_{i=1}^n i=\sum\limits_{k=1}^n\sum\limits_{d|k} \varphi(d) \]

左式是懂的都懂的 \(\frac{1}{2}n(n+1)\)

对于右式考虑交换求和顺序:

\[=\sum\limits_{d=1}^n\sum\limits_{1\leq k\leq n,d|k} \varphi(\frac{k}{d}) \]

\[=\sum\limits_{d=1}^n\sum\limits_{1\leq k\leq n,d|k} \varphi(\frac{k}{d}) \]

\[=\sum\limits_{d=1}^n\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor} \varphi(j) \]

\[=\sum\limits_{d=1}^n\phi(\lfloor\frac{n}{d}\rfloor) \]

于是我们得到了这样一个等式:

\[\frac{1}{2}n(n+1)=\sum\limits_{d=1}^n\phi(\lfloor\frac{n}{d}\rfloor) \]

然后显然当 \(d=1\) 时右式就是 \(\phi(n)\),所以:

\[\frac{1}{2}n(n+1)=\phi(n)+\sum\limits_{d=2}^n\phi(\lfloor\frac{n}{d}\rfloor) \]

即:

\[\phi(n)=\frac{1}{2}n(n+1)-\sum\limits_{d=2}^n\phi(\lfloor\frac{n}{d}\rfloor) \]

这是我们就可以直接递归下去了,边界是 \(\phi(1)=1,\phi(0)=0\)

中间采用整除分块转移。

但是这样复杂度不会有问题吗,因为递归下去不会相当于是将 \(\phi(1\sim n)\) 全部求一遍吗。

实际上我们如果称 \(\lfloor\frac{n}{d}\rfloor\) 为特征点的话,那么特征点的特征点即为 \(\lfloor\frac{n}{dd'}\rfloor\),显然这也是第一代特征点。

所以我们所有需要求的即为所有特征点,可以证明此时的复杂度为 \(O(n^{3/4})\)

这看上去很好了,但是还有一个显然的优化,就是可以将前 \(S\) 项的 \(\phi\) 直接线性筛预处理出来。

这样就不必考虑递归边界的问题,而且加快了时间。

可以证明当 \(S=n^{2/3}\) 时得到了最优复杂度为 \(O(n^{2/3})\)

这就是杜教筛的时间,反观上面的要求,显然 \(Id\)\(1\) 两个函数我们都可以用 \(O(1)\) 的时间算出前缀和。

这显然是不弱于杜教筛的时间。

当然还有更加普遍的形式,假设我们有 \(h=f*g\),要求 \(S=\sum_{i=1}^nf(i)\)

\[\sum\limits_{i=1}^n h(i)=\sum\limits_{k=1}^n\sum\limits_{d|k} f(d)g(\frac{k}{d}) \]

\[=\sum\limits_{d=1}^n\sum\limits_{1\leq k\leq n,d|k} f(\frac{k}{d})g(d) \]

\[=\sum\limits_{d=1}^ng(d)\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor} f(j) \]

\[=\sum\limits_{d=1}^ng(d)\cdot S(\lfloor\frac{n}{d}\rfloor) \]

即:

\[g(1)S(n)=\sum\limits_{i=1}^n h(i)-\sum\limits_{d=2}^ng(d)\cdot S(\lfloor\frac{n}{d}\rfloor) \]

再次强调一遍,如果我们可以在不弱于杜教筛的时间内求出 \(g,h\) 的值,那么既可以杜教筛出 \(f\) 的前缀和。

现在看起来这个要求就十分显然了。

【简单应用】

可以筛的东西很多,比如说 \(\mu\) 函数。

我们知道 \(\mu * 1=\epsilon\),所以可以直接套公式得到:

\[M(n)=1-\sum\limits_{d=2}^n M(\lfloor\frac{n}{d}\rfloor) \]

然后结合上面的 \(\phi\) 的求值,可以解决这道模板题

代码贴一下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<unordered_map>
using namespace std;

typedef long long LL;
#define PII pair<LL, int>
#define MP make_pair
const int M = 1700000;
int n, tot, prm[M + 10], mu[M + 10];
LL phi[M + 10]; bool vis[M + 10];
unordered_map<int, LL> Phi;
unordered_map<int, int> Mu;

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

void Pre(){
    phi[1] = mu[1] = 1;
    for(int i = 2; i <= M; i ++){
        if(!vis[i]){
            prm[++ tot] = i;
            phi[i] = i - 1; mu[i] = -1;
        }
        for(int j = 1; j <= tot && prm[j] * i <= M; j ++){
            vis[prm[j] * i] = true;
            if(i % prm[j]){
                phi[prm[j] * i] = phi[i] * (prm[j] - 1);
                mu[prm[j] * i] = - mu[i];
            }
            else{
                phi[prm[j] * i] = phi[i] * prm[j];
                mu[prm[j] * i] = 0;
                break;
            }
        }
        phi[i] += phi[i - 1];
        mu[i] += mu[i - 1];
    }
}

PII Get(LL x){
    if(x <= M) return MP(phi[x], mu[x]);
    if(Phi[x]) return MP(Phi[x], Mu[x]);
    LL a = 1LL * x * (x + 1LL) / 2LL;
    LL b = 1;
    for(LL l = 2, r; l <= x; l = r + 1){
        r = min(x / (x / l), x);
        PII now = Get(x / l);
        a -= 1LL * (r - l + 1) * now.first;
        b -= 1LL * (r - l + 1) * now.second;
    }
    Phi[x] = a, Mu[x] = b;
    return MP(a, b);
}

int main(){
    int T = read();
    Pre();
    while(T --){
        n = read();
        PII ans = Get(n);
        printf("%lld %d\n", ans.first, ans.second);
    }
    return 0;
}

这里用到了 unordered_map 以保证复杂度,防止多个 \(\log\),考试时有时间可以写个 hash。

但是好像局部多个 \(\log\) 并不影响整体复杂度就是了。

【总结】

杜教筛的题目五花八门的,后续慢慢补充吧(咕咕咕)

比如说求 \(S=\sum_{i=1}^n i\cdot\varphi(i)\),然后你会发现 \(S*Id=Id^2\),于是没了。

完结撒花。

posted @ 2021-06-25 23:51  LPF'sBlog  阅读(62)  评论(1编辑  收藏  举报