「笔记」杜教筛

前置知识

积性函数相关内容
整除分块

务必较为熟练地掌握上述知识。以下推导均建立在上述知识上展开。

简介

可在低于线性时间的复杂度内 处理数论函数的前缀和。
对于一个数论函数 \(f(n)\),要求快速求解:

\[\sum_{i=1}^{n}f(i) \]

\(S(n) = \sum\limits_{i=1}^{n}f(i)\),则对于任何一个数论函数 \(g\),必满足:

\[\sum_{i=1}^{n}(f\ast g)(i) = \sum_{i=1}^{n}g(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right) \]

\(i=1\) 提出并移项,则可得到递推式:

\[g(1)S(n) = \sum_{i=1}^{n}(f\ast g)(i) - \sum_{i=2}^{n}g(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right) \]

发现后一项可以数论分块求解。
若可以快速求解前一项的卷积,就可以快速求得 \(g(1)S(n)\)

证明

这是一个看起来很扯的式子:

\[\sum_{i=1}^{n}(f\ast g)(i) = \sum_{i=1}^{n}g(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right) \]

只考虑等式右侧,展开卷积:

\[\sum_{i=1}^{n}(f\ast g)(i) = \sum_{i=1}^n {\sum_{d\mid i}g(d)\cdot f(\dfrac{i}{d})} \]

考虑交换枚举顺序,先枚举 \(d\)
\(d \mid i\),则 \(i\)\(d\) 的倍数(废话)。
\(d\) 会对其倍数 \(kd\) 作出 \(g(d)\cdot f(\dfrac{kd}{d}) = g(d)\cdot f(k)\) 的贡献。

\(1\sim n\)\(d\) 的倍数为 \(d\sim \left\lfloor\dfrac{n}{d}\right\rfloor d\),则有下式:

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

得证。

例题

基础知识是不是非常水

板子题

P4213 【模板】杜教筛(Sum)

\(T\) 组数据,每组数据给定 \(n\),求:

\[\begin{aligned} \sum_{i=1}^{n} \varphi(i)\\ \sum_{i=1}^{n}\mu (i) \end{aligned}\]

\(1\le T\le 10,1\le n\le 2^{31}\)

设要求解的函数为 \(f\),考虑找到一个函数 \(g\),构造一个可以快速求值的函数 \(f\ast g\)

对于子任务 1,考虑 \(\varphi\) 的性质:

\[\varphi \ast 1 = \operatorname{Id} \]

\(\varphi\)\(1\) 卷起来,得到了易于求值的幂函数 \(\operatorname{Id}\)
直接代入式子:

\[\begin{aligned} g(1)S(n) &= \sum_{i=1}^{n}(f\ast g)(i) - \sum_{i=2}^{n}g(i)S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)\\ S(n) &= \sum_{i=1}^{n}\operatorname{Id}(i) - \sum_{i=2}^{n}S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)\\ S(n) &= \dfrac{n(n+1)}{2} - \sum_{i=2}^{n}S\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right) \end{aligned}\]

数论分块求解即可。


对于子任务 2,考虑 \(\mu\) 的性质。

\[\mu\ast 1 = e \]

\(\mu\)\(1\) 卷起来,得到了单位元 \(e\)
带入式子,这个更优美了:

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

数论分块求解即可。

代码

//知识点:杜教筛
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#define ll long long
const int kMaxn = 5e6 + 10;
const int kInf = 0x7fffffff;
const int kMaxPrimeNum = 348521 + 10;
//=============================================================
int limit = 5e6 + 10, pnum, prime[kMaxPrimeNum];
int mu[kMaxn];
ll phi[kMaxn];
bool vis[kMaxn];
std :: unordered_map <int, ll> kn_phi; //kn = known
std :: unordered_map <int, int> kn_mu;
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Prepare() {
  mu[1] = phi[1] = 1;
  for (int i = 2; i <= limit; ++ i) {
    if (! vis[i]) {
      prime[++ pnum] = i;
      mu[i] = - 1;
      phi[i] = i - 1;
    }

    for (int j = 1; j <= pnum; ++ j) {
      if (prime[j] * i > limit) break;
      int pj = prime[j];
      vis[pj * i] = true;
      if (! (i % pj)) {
        phi[i * pj] = phi[i] * pj;
        break;
      }
      phi[i * pj] = phi[i] * phi[pj];
      mu[i * pj] = - mu[i];
    }
  }

  for (int i = 1; i <= limit; ++ i) {
    mu[i] += mu[i - 1];
    phi[i] += phi[i - 1];
  }
}
ll SumPhi(int n) {
  if (n <= limit) return phi[n];
  if (kn_phi.count(n)) return kn_phi[n];
  ll ret = 1ll * n * (1ll * n + 1) >> 1;
  for (int l = 2, r; r < kInf && l < n; l = r + 1) {
    r = n / (n / l);
    ret -= 1ll * (r - l + 1) * SumPhi(n / l);
  }
  return (kn_phi[n] = ret);
}
ll SumMu(int n) {
  if (n <= limit) return mu[n];
  if (kn_mu.count(n)) return kn_mu[n];
  int ret = 1;
  for (int l = 2, r; r < kInf && l < n; l = r + 1) {
    r = n / (n / l);
    ret -= (r - l + 1) * SumMu(n / l);
  }
  return (kn_mu[n] = ret);
}
//=============================================================
int main() {
  Prepare();
  int T = read();
  while (T --) {
    int n = read();
    printf("%lld %d\n", SumPhi(n), SumMu(n));
  }
  return 0;
}

感悟

杜教筛的过程,相当于找到一个函数 \(g\)
构造一个可以快速求值的函数 \(h = f\ast g\)

那要怎么找?大胆猜测
一般是常见的函数,建议多做题多感悟。

写在最后

参考资料:

杜教筛 - pengym
oi-wiki

在别人作画的时候随便加上两笔?
这太野蛮了,太谔谔了,
我不承认这是人做出来的事。

posted @ 2020-08-07 20:29  Luckyblock  阅读(189)  评论(4编辑  收藏  举报