关于杜教筛

杜教筛是对某种函数快速求前缀和(不是所有,是固定参数的)的做法,经过预处理优化,一般能够优化到 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32) 的时间复杂度。它由杜瑜皓引进,所以称为杜教筛。

原理

假设函数 f ( n ) , g ( n ) f(n),g(n) f(n),g(n) 是积性函数, h = f ∗ g h = f * g h=fg,则函数 h ( n ) h(n) h(n) 是积性函数。

F , G , H F,G,H F,G,H 分别是 f , g , h f,g,h f,g,h 的前缀和。

此时我们给定 n n n ,需要快速求出 F ( n ) F(n) F(n)

因为
h ( n ) = ∑ d ∣ n f ( d ) ⋅ g ( n d ) = f ( n ) ⋅ g ( 1 ) + ∑ d ∣ n , d < n f ( d ) ⋅ g ( n d ) \begin{aligned} h(n) &= \sum_{d|n}f(d)\cdot g(\frac{n}{d})\\ &=f(n)\cdot g(1)+\sum_{d|n,d<n}f(d)\cdot g(\frac{n}{d}) \end{aligned} h(n)=dnf(d)g(dn)=f(n)g(1)+dn,d<nf(d)g(dn)

所以

∑ i = 1 n h ( i ) = ∑ i = 1 n f ( i ) ⋅ g ( 1 ) + ∑ i = 1 n ∑ d ∣ n , d < i f ( d ) ⋅ g ( i d ) \sum_{i = 1}^n h(i)=\sum_{i = 1}^n f(i) \cdot g(1)+\sum_{i=1}^n\sum_{d|n,d<i}f(d)\cdot g(\frac{i}{d}) i=1nh(i)=i=1nf(i)g(1)+i=1ndn,d<if(d)g(di)

此时,我们设 j = i / d j = i/d j=i/d ,则有

∑ i = 1 n h ( i ) = H ( n ) = ∑ i = 1 n f ( i ) ⋅ g ( 1 ) + ∑ j = 2 n g ( j ) ∑ d = 1 n / j f ( d ) = F ( n ) ⋅ g ( 1 ) + ∑ j = 2 n g ( j ) ⋅ F ( n j ) \begin{aligned} \sum_{i = 1}^n h(i)&=H(n)\\ &=\sum_{i = 1}^n f(i) \cdot g(1)+\sum_{j=2}^n g(j)\sum_{d = 1}^{n/j}f(d)\\ &=F(n)\cdot g(1)+\sum_{j = 2}^n g(j)\cdot F(\frac{n}{j}) \end{aligned} i=1nh(i)=H(n)=i=1nf(i)g(1)+j=2ng(j)d=1n/jf(d)=F(n)g(1)+j=2ng(j)F(jn)

因为 g ( 1 ) = 1 g(1)=1 g(1)=1 ,则可以写为

F ( n ) = H ( n ) − ∑ j = 2 n g ( j ) ⋅ F ( n j ) F(n)=H(n)-\sum_{j=2}^ng(j)\cdot F(\frac{n}{j}) F(n)=H(n)j=2ng(j)F(jn)

如果 H ( n ) H(n) H(n) G ( j ) G(j) G(j) 可以快速求(比如 O ( 1 ) O(1) O(1) 求),我们就得到了一个关于 F ( n ) F(n) F(n) 的递推式。

此时用数论分块可以降到 O ( n 3 4 ) O(n^{\frac{3}{4}}) O(n43) 的时间复杂度。

如果我们将前 n 2 3 n^{\frac{2}{3}} n32 F F F 数组预处理出来,则时间可以降为 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32)

例题

例题一

∑ i = 1 n ϕ ( i ) \sum_{i=1}^n \phi(i) i=1nϕ(i) ∑ i = 1 n μ ( i ) \sum_{i=1}^n \mu(i) i=1nμ(i)

其中 n ≤ 2 31 − 1 n\leq2^{31}-1 n2311

思路

板题,简单说说。

因为我们知道 ϕ ∗ I = I d \phi*I=Id ϕI=Id ,并且 I , I d I,Id I,Id 的前缀和都可以 O ( 1 ) O(1) O(1) 求,此时就有

∑ i = 1 n ϕ ( i ) = n ⋅ ( n + 1 ) 2 − ∑ j = 2 n I ( j ) ∑ i = 1 n / j ϕ ( i ) \sum_{i=1}^{n}\phi(i) =\frac{n\cdot(n+1)}{2}-\sum_{j=2}^{n}I(j)\sum_{i=1}^{n/j}\phi(i) i=1nϕ(i)=2n(n+1)j=2nI(j)i=1n/jϕ(i)

此时设 f ( n ) = ∑ i = 1 n ϕ ( i ) f(n)=\sum_{i=1}^{n}\phi(i) f(n)=i=1nϕ(i) ,则有

f ( n ) = n ⋅ ( n + 1 ) 2 − ∑ j = 2 n I ( j ) ⋅ f ( n / j ) f(n) =\frac{n\cdot(n+1)}{2}-\sum_{j=2}^{n}I(j)\cdot f(n/j) f(n)=2n(n+1)j=2nI(j)f(n/j)

因为 μ ∗ I = ϵ \mu*I=\epsilon μI=ϵ ,所以同上推导就可以了。

这就是杜教筛的模板。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 5000000;
int T, n, ptot, prime[MAXN + 10], phi[MAXN + 10], mu[MAXN + 10];
bool bz[MAXN + 10];
LL premu[MAXN + 10], prephi[MAXN + 10];
map<LL, LL> mpphi, mpmu;
void get(int n) {
    phi[1] = mu[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!bz[i])
            ptot++, prime[ptot] = i, phi[i] = i - 1, mu[i] = -1;
        for (int j = 1; j <= ptot && prime[j] * i <= n; j++) {
            bz[prime[j] * i] = true;
            if (i % prime[j] == 0) {
                phi[i * prime[j]] = phi[i] * prime[j];
                mu[i * prime[j]] = 0;
                break;
            } else {
                phi[i * prime[j]] = phi[i] * (prime[j] - 1);
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
    for (int i = 1; i <= n; i++) premu[i] = premu[i - 1] + mu[i], prephi[i] = prephi[i - 1] + phi[i];
}
LL dfsphi(int n) {
    if (n <= MAXN)
        return prephi[n];
    if (mpphi.count(n))//如果之前求过,就把值拿来用
        return mpphi[n];
    LL sum = 0;
    for (int i = 2; i <= n; i++) {
        int last = i - 1;
        i = n / (n / i);
        sum = sum + dfsphi(n / i) * (i - last);
        if (i == n)//因为i加1后有可能会爆int
            break;
    }
    return mpphi[n] = n * (n + 1ll) / 2 - sum;
}
LL dfsmu(int n) {
    if (n <= MAXN)
        return premu[n];
    if (mpmu.count(n))//如果之前求过,就把值拿来用
        return mpmu[n];
    LL sum = 0;
    for (int i = 2; i <= n; i++) {
        int last = i - 1;
        i = n / (n / i);
        sum = sum + dfsmu(n / i) * (i - last);
        if (i == n)//因为i加1后有可能会爆int
            break;
    }
    return mpmu[n] = 1 - sum;
}
int main() {
    scanf("%d", &T);
    get(MAXN);//预处理处前n个前缀和
    while (T--) {
        scanf("%d", &n);
        printf("%lld %lld\n", dfsphi(n), dfsmu(n));
    }
    return 0;
}

例题二

∑ i = 1 n ϕ ( i 2 ) \sum_{i=1}^n \phi(i^2) i=1nϕ(i2) 1 0 9 + 7 10^9+7 109+7 的结果。

其中 n ≤ 1 0 9 n\leq10^9 n109

思路

我们要知道一个性质,即 ϕ ( i 2 ) = i ⋅ ϕ ( i ) \phi(i^2)=i\cdot \phi(i) ϕ(i2)=iϕ(i)

所以原式就变成了 ∑ i = 1 n i ⋅ ϕ ( i ) \sum_{i=1}^n i\cdot \phi(i) i=1niϕ(i)

因为 n 2 = n ∑ i ∣ n p h i ( i ) = ∑ i ∣ n ϕ ( i ) ⋅ i ⋅ n i n^2=n\sum_{i|n} phi(i)=\sum_{i|n}\phi(i)\cdot i\cdot\frac{n}{i} n2=ninphi(i)=inϕ(i)iin

所以 n ⋅ ϕ ( n ) = n 2 − ∑ i ∣ n , i < n ϕ ( i ) ⋅ i ⋅ n i n\cdot \phi(n)=n^2-\sum_{i|n,i<n}\phi(i)\cdot i\cdot \frac{n}{i} nϕ(n)=n2in,i<nϕ(i)iin

此时我们设 f ( i ) = ϕ ( i 2 ) = i ⋅ ϕ ( i ) f(i)=\phi(i^2)=i\cdot \phi(i) f(i)=ϕ(i2)=iϕ(i) ,则有 f ( n ) = n 2 − ∑ i ∣ n , i < n f ( i ) ⋅ n i f(n)=n^2-\sum_{i|n,i<n}f(i)\cdot\frac{n}{i} f(n)=n2in,i<nf(i)in

我们设 F ( n ) = ∑ i = 1 n f ( i ) F(n) = \sum_{i=1}^nf(i) F(n)=i=1nf(i) ,则

F ( n ) = ∑ i = 1 n f ( i ) = ∑ i = 1 n i 2 − ∑ i = 1 n ∑ j ∣ i , j < i f ( j ) ⋅ i j = ∑ i = 1 n i 2 − ∑ d = 2 n d ∑ i = 1 n / d f ( i ) = ∑ i = 1 n i 2 − ∑ d = 2 n d ⋅ F ( n d ) \begin{aligned} F(n)&=\sum_{i=1}^nf(i)\\ &=\sum_{i=1}^ni^2-\sum_{i=1}^n\sum_{j|i,j<i}f(j)\cdot\frac{i}{j}\\ &=\sum_{i=1}^ni^2-\sum_{d=2}^nd\sum_{i=1}^{n/d}f(i)\\ &=\sum_{i=1}^ni^2-\sum_{d=2}^nd\cdot F(\frac{n}{d}) \end{aligned} F(n)=i=1nf(i)=i=1ni2i=1nji,j<if(j)ji=i=1ni2d=2ndi=1n/df(i)=i=1ni2d=2ndF(dn)

此时就用杜教筛来做就行了。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 5000000;
const LL MOD = 1e9 + 7;
const LL rev = 166666668;
int T, n, ptot, prime[MAXN + 10];
bool bz[MAXN + 10];
LL prephi[MAXN + 10], phi[MAXN + 10];
map<LL, LL> mpphi;
void get(int n) {
    phi[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!bz[i])
            ptot++, prime[ptot] = i, phi[i] = 1ll * (i - 1) * i % MOD;
        for (int j = 1; j <= ptot && prime[j] * i <= n; j++) {
            bz[prime[j] * i] = true;
            if (i % prime[j] == 0) {
                phi[i * prime[j]] = phi[i] * prime[j] % MOD * prime[j] % MOD;
                break;
            } else {
                phi[i * prime[j]] = phi[i] * (prime[j] - 1) % MOD * prime[j] % MOD;
            }
        }
    }
    for (int i = 1; i <= n; i++) prephi[i] = (prephi[i - 1] + phi[i]) % MOD;
}
LL dfsphi(int n) {
    if (n <= MAXN)
        return prephi[n];
    if (mpphi.count(n))
        return mpphi[n];
    LL sum = 0;
    for (int i = 2; i <= n; i++) {
        int last = i - 1;
        i = n / (n / i);
        sum = (sum + (1ll * (i - last) * (i + last + 1) / 2) % MOD * dfsphi(n / i) % MOD) % MOD;
    }
    return mpphi[n] = (1ll * n * (n + 1ll) % MOD * (2ll * n + 1ll) % MOD * rev % MOD - sum + MOD) % MOD;
}
int main() {
    get(MAXN);//这里求的是phi(i)的平方的前缀和
    scanf("%d", &n);
    printf("%lld\n", dfsphi(n));
    return 0;
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值