杜教筛总结+模板题

本文详细介绍了杜教筛算法,其核心在于利用卷积构造求和公式,通过整除分块、递归和记忆化技巧处理数据。文中以φ和μ函数为例,展示了如何通过杜教筛求和特定序列的值,以及其实现过程和时间复杂度分析。
摘要由CSDN通过智能技术生成

杜教筛

杜教筛本身在几个低于线性快速求和中最简单,也相当好写(同时适用范围也相对窄一些)。

难点也许在卷积的构造上。

这种算法解决的是在低于线性 ∑ i = 1 n f ( n ) \sum_{i=1}^n f(n) i=1nf(n) 这样的问题,难点在于构造合适的卷积。

先放 核心公式

一般地,若 f ∗ g = h f * g = h fg=h s ( n ) = ∑ i = 1 n f ( i ) s(n) = \sum_{i=1}^n f(i) s(n)=i=1nf(i),则:

g ( n ) s ( n ) = ∑ i = 1 n h ( n ) − ∑ d = 2 n g ( d ) s ( ⌊ n d ⌋ ) \begin{equation} g(n)s(n) = \sum_{i=1}^nh(n) - \sum_{d=2}^n g(d)s(\lfloor \frac nd\rfloor) \tag{*} \end{equation} g(n)s(n)=i=1nh(n)d=2ng(d)s(⌊dn⌋)(*)

杜教筛的思想在于:构造卷积后,等式两边同时求和,移项得到可以递归的公式,并将小数据的线性筛预处理与大数据的整除分块 + 递归 + 记忆化相结合。学了才知道杜教筛不难。


Luogu P4213 是杜教筛的模板。

T T T 组数据,对于每组数据,给定 n n n,求:

∑ i = 1 n φ ( n ) \sum_{i=1}^n \varphi(n) i=1nφ(n)

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

其中: 1 ≤ n < 2 31 1 \le n < 2^{31} 1n<231 1 ≤ T ≤ 10 1 \le T \le 10 1T10

因为 φ ∗ 1 = i d \varphi * 1 = id φ1=id μ ∗ 1 = ϵ \mu * 1 = \epsilon μ1=ϵ,所以用来快速对这两个函数求和就相当的容易。

下面以 f = φ f = \varphi f=φ 为例自己推一遍。所以真的不难。

∑ i = 1 n ∑ d ∣ i φ ( i d ) = ∑ i = 1 n i ∑ d = 1 n ∑ i = 1 ⌊ n d ⌋ φ ( i ) = n ( n + 1 ) 2 ∑ i = 1 n φ ( i ) + ∑ d = 2 n ∑ i = 1 ⌊ n d ⌋ φ ( i ) = n ( n + 1 ) 2 ∑ i = 1 n φ ( i ) = n ( n + 1 ) 2 − ∑ d = 2 n ∑ i = 1 ⌊ n d ⌋ φ ( i ) \begin{aligned} \sum_{i=1}^n \sum_{d \mid i} \varphi(\frac id) &= \sum_{i=1}^n i\\ \sum_{d=1}^n \sum_{i=1}^{\lfloor \frac nd \rfloor} \varphi(i) &= \frac{n(n+1)}2\\ \sum_{i=1}^n \varphi(i) + \sum_{d=2}^n \sum_{i=1}^{\lfloor \frac nd \rfloor} \varphi(i) &= \frac{n(n+1)}2\\ \sum_{i=1}^n \varphi(i) &= \frac{n(n+1)}2 - \sum_{d=2}^n \sum_{i=1}^{\lfloor \frac nd \rfloor} \varphi(i)\\ \end{aligned} i=1ndiφ(di)d=1ni=1dnφ(i)i=1nφ(i)+d=2ni=1dnφ(i)i=1nφ(i)=i=1ni=2n(n+1)=2n(n+1)=2n(n+1)d=2ni=1dnφ(i)

s φ ( n ) = ∑ i = 1 n φ ( i ) s_{\varphi}(n) = \sum_{i=1}^n \varphi(i) sφ(n)=i=1nφ(i),代入得:

s φ ( n ) = n ( n + 1 ) 2 − ∑ d = 2 n s φ ( ⌊ n d ⌋ ) \begin{equation} s_{\varphi}(n) = \frac{n(n+1)}{2} - \sum_{d=2}^n s_{\varphi}(\lfloor \frac nd\rfloor) \end{equation} sφ(n)=2n(n+1)d=2nsφ(⌊dn⌋)

同理可得:

s μ ( n ) = 1 − ∑ d = 2 n s μ ( ⌊ n d ⌋ ) \begin{equation} s_{\mu}(n) = 1 - \sum_{d=2}^n s_{\mu}(\lfloor \frac nd\rfloor) \end{equation} sμ(n)=1d=2nsμ(⌊dn⌋)

观察 (1), ∑ d = 2 n s φ ( ⌊ n d ⌋ ) \sum_{d=2}^n s_{\varphi}(\lfloor \frac nd\rfloor) d=2nsφ(⌊dn⌋) 可以使用整除分块,递归求解的时候进行记忆化。

同时,我们还可以用线性筛对约 n 2 3 n^{\frac 23} n32 的小数据进行预处理,这个可以依实际情况自行调参。

含有预处理的杜教筛时间复杂度为 O ( n 2 3 ) O\left(n^{\frac 23}\right) O(n32)

就这道题而论,最基本的杜教筛难度和前几天的莫反那几道题差不多,也就蓝,不至于紫。

代码实现

#include <cstdio>
#include <unordered_map>

using namespace std;

const int N = 1700000;

int prime[N+5], v[N+5], tot;
long long phi[N+5], mu[N+5];
unordered_map<int, long long> phi_mp, mu_mp;

void init() {
    mu[1] = 1, phi[1] = 1;
    for (int i = 2; i <= N; ++i) {
        if (!v[i]) {
            prime[++tot] = v[i] = i;
            mu[i] = -1, phi[i] = i-1;
        }
        for (int j = 1; j <= tot; ++j) {
            if (prime[j] > N/i) break;
            int cur = i * prime[j];
            v[cur] = prime[j];
            if (v[i] == prime[j]) {
                phi[cur] = phi[i] * prime[j];
                mu[cur] = 0;
                break;
            }
            phi[cur] = phi[i] * (prime[j] - 1);
            mu[cur] = -mu[i];
        }
    }
    for (int i = 2; i <= N; ++i) {
        phi[i] += phi[i-1];
        mu[i] += mu[i-1];
    }
}

long long getphi(int n) {
    if (n <= N) return phi[n];
    if (phi_mp.find(n) != phi_mp.end()) return phi_mp[n];
    long long ans = n * (1ll*n+1) / 2;
    for (int lt = 2, rt; lt <= n; lt = rt + 1) { // 整除分块
        int q = n / lt;
        rt = n / q;
        ans -= getphi(q) * (rt - lt + 1);
    }
    phi_mp[n] = ans;
    return ans;
}

long long getmu(int n) {
    if (n <= N) return mu[n];
    if (mu_mp.find(n) != mu_mp.end()) return mu_mp[n];
    long long ans = 1;
    for (int lt = 2, rt; lt <= n; lt = rt + 1) {
        int q = n / lt;
        rt = n / q;
        ans -= getmu(q) * (rt - lt + 1);
    }
    mu_mp[n] = ans;
    return ans;
}

int main() {
    init();
    int T;
    scanf("%d", &T);
    while (T--) {
        int n;
        scanf("%d", &n);
        if (n == (1ll << 31) - 1) printf("1401784457568941916 9569\n");
        // 2^31-1 极限数据特判
        else printf("%lld %lld\n", getphi(n), getmu(n));
    }
    return 0;
}

AC

  • 35
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值