杜教筛与 Powerful Number 筛杂谈

· · 算法·理论

本文的所有函数均指数论函数,即定义域为正整数的函数。

前置

你需要先了解的知识。定理只放式子,不给证明。

杜教筛

原先是写在这篇题解的,直接照搬过来稍微修改一下。

杜教筛用于求满足 f*g=h 的数论函数 f,其中 gh 是另外两个方便计算的数论函数。

我们定义 S(n)=\displaystyle\sum_{i=1}^nf(i)

杜教筛基于这个基本式子:

\sum_{i=1}^nh(i)=\sum_{i=1}^ng(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)

证明:

\begin{aligned} \sum_{i=1}^nh(i)&=\sum_{i=1}^n(f*g)(i)\\ &=\sum_{i=1}^n(g*f)(i)\\ &=\sum_{i=1}^n\sum_{d \mid i}g(d)f\left(\dfrac{i}{d}\right)\\ &=\sum_{d=1}^n\sum_{i=1}^n[d \mid i]g(d)f\left(\dfrac{i}{d}\right)\\ &=\sum_{d=1}^n\sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor}g(d)f(i)\\ &=\sum_{d=1}^ng(d)\sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor}f(i)\\ &=\sum_{d=1}^ng(d)S\left(\left\lfloor\dfrac{n}{d}\right\rfloor\right)\\ &=\sum_{i=1}^ng(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right) \end{aligned}

那么在这个等式两边同时减去 \displaystyle\sum_{i=2}^ng(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right),则有:

\begin{aligned} \left(\sum_{i=1}^ng(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)\right)-\sum_{i=2}^ng(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)&=\left(\sum_{i=1}^nh(i)\right)-\sum_{i=2}^ng(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)\\ g(1)S(n)&=\left(\sum_{i=1}^nh(i)\right)-\sum_{i=2}^ng(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)\\ S(n)&=\dfrac{1}{g(1)}\left(\left(\sum_{i=1}^nh(i)\right)-\sum_{i=2}^ng(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)\right) \end{aligned}

这个式子可以直接数论分块,递归解决即可,边界条件为 n=1,此时结果为 f(1)。我们需要在计算过程中将 S(n) 存储下来。

时间复杂度

由于记忆化的存在,杜教筛的时间复杂度不是很方便分析。

我们考虑画出递归树。以 n=12 为例:

加上记忆化后,一些递归树被砍掉了:

尝试从右往左计算,而不是从左往右:

可以看到,这时的递归树只剩一层了!事实上,由于 \left\lfloor\dfrac{\left\lfloor\frac{a}{b}\right\rfloor}{c}\right\rfloor=\left\lfloor\dfrac{a}{bc}\right\rfloor,那么在深层计算的一定在浅层出现。

并且容易发现,改变计算顺序后复杂度不变(因为每个 n 始终会递归恰好一次)。

所以复杂度为:

\sum_{i \in S}\sqrt i

其中 S 是由所有不同的 \left\lfloor\dfrac{n}{i}\right\rfloor 构成的集合。

用和数论分块一样的方法,它渐进等于:

\sum_{i=1}^{\sqrt n}\sqrt i+\sqrt{\dfrac{n}{i}}

显然 \displaystyle\sum_{i=1}^{\sqrt n}\sqrt i 在这里是可以忽略的(因为它不会大于右边)。我们只需要计算:

\sum_{i=1}^{\sqrt n}\sqrt{\dfrac{n}{i}}

根据这篇文章中的技巧,我们将和式转换成定积分,在渐进意义上相等。因此计算:

\begin{aligned} T(n)&\asymp\sum_{i=1}^{\sqrt n}\sqrt{\dfrac{n}{i}}\\ &\sim\int_1^{\sqrt n}\sqrt{\dfrac{n}{x}}\mathrm dx\\ &\sim\int_0^{\sqrt n}\sqrt{\dfrac{n}{x}}\mathrm dx\\ &=\int_0^{\sqrt n}\dfrac{\sqrt n}{\sqrt x}\mathrm dx\\ &=\sqrt n\int_0^{\sqrt n}x^{-0.5}\mathrm dx\\ &=\sqrt n\dfrac{(\sqrt n)^{0.5}}{0.5}\\ &=2n^{0.75} \end{aligned}

因此不加优化的杜教筛复杂度为 \Theta\left(n^{\frac{3}{4}}\right)

优化 1:预处理

线性筛预处理 S(1),S(2),\cdots,S(c)\;[c \ge \sqrt n],复杂度为:

\begin{aligned} T(n)&\asymp\Theta(c)+\sum_{i=1}^{\sqrt n}\left[\dfrac{n}{i} \ge c\right]\sqrt{\dfrac{n}{i}}\\ &\asymp c+\sum_{i=1}^{\sqrt n}\left[i \le \dfrac{n}{c}\right]\sqrt{\dfrac{n}{i}}\\ &=c+\sum_{i=1}^{\frac{n}{c}}\sqrt{\dfrac{n}{i}}\\ &\sim c+\int_1^{\frac{n}{c}}\sqrt{\dfrac{n}{x}}\mathrm dx\\ &\sim c+\int_0^{\frac{n}{c}}\sqrt{\dfrac{n}{x}}\mathrm dx\\ &=c+\sqrt n\int_0^{\frac{n}{c}}x^{-0.5}\mathrm dx\\ &=c+\sqrt n\dfrac{\left(\frac{n}{c}\right)^{0.5}}{0.5}\\ &=c+\dfrac{2n}{\sqrt c}\\ &\asymp c+\dfrac{n}{\sqrt c} \end{aligned}

显然,当加号两边相等时结果最小。

\begin{aligned} c&=nc^{-\frac{1}{2}}\\ c^{\frac{3}{2}}&=n\\ c&=n^{\frac{2}{3}} \end{aligned}

因此,当预处理 n^{\frac{2}{3}} 以内的结果时复杂度最小,为 \Theta\left(n^{\frac{2}{3}}\right)

优化 2:记忆化

我们的记忆化需要用到 map 或哈希,而这会增大常数(如果用 map 还会使复杂度增加一个 \log),这是我们不希望看到的。于是有了一种更简便的方法:加入一个数组 h,记录 h_x=S\left(\left\lfloor\dfrac{n}{x}\right\rfloor\right),由于当 x \le n^{\frac{2}{3}} 时会直接访问线性筛,h 数组的大小开到 n^{\frac{1}{3}} 即可。

这个 x 如何确定?可以证明,如果 a=\left\lfloor\dfrac{n}{b}\right\rfloor,那么 \left\lfloor\dfrac{n}{a}\right\rfloor 一定可以作为 x

请注意,这种方法对多组重复数据不友好(因为每次 h 数组都要清空)。

代码

这是 \varphi 函数的代码。

constexpr int MAXN = 1000006; // n^2/3
constexpr ll P = 1e9+7;
bool isp[MAXN];
int primes[MAXN];
ll phi[MAXN];
ll h[1003]; // n^1/3

template <typename T>
inline T mod(T n){
    T ans = n%P;
    return (ans<0)?ans+P:ans;
}

template <typename T>
inline T pow(T x, T y){
    if (x >= P) x %= P;
    if (y >= P-1) y %= P-1;
    T ans = 1;
    while (y){
        if (y&1) ans = (ans*x)%P;
        x = (x*x)%P;
        y >>= 1;
    }
    return ans;
}

void init(){
    memset(isp, 1, sizeof isp);
    int cnt = 0;
    phi[1] = 1;
    for (int i = 2; i < MAXN; ++i){
        if (isp[i]) primes[cnt++] = i, phi[i] = i-1;
        for (int j = 0; j < cnt && i*primes[j] < MAXN; ++j){
            isp[i*primes[j]] = 0;
            if (!(i%primes[j])){
                phi[i*primes[j]] = phi[i]*primes[j]; break;
            }
            phi[i*primes[j]] = phi[i]*phi[primes[j]];
        }
    }
    for (int i = 2; i < MAXN; ++i) phi[i] = (phi[i]+phi[i-1])%P;
}

ll N;

ll sum_phi(ll n){
    if (n < MAXN) return phi[n];
    int x = N/n;
    if (h[x]) return h[x];
    ll ans = (n*(n+1)>>1)%P;
    for (ll l = 2, r; l <= n; l = r+1){
        r = n/(n/l);
        ans = mod(ans-(r-l+1)*sum_phi(n/l));
    }
    return h[x]=ans;
}

(待更新)