狄利克雷卷积(Dirichlet)& 杜教筛

狄利克雷卷积(Dirichlet)

基础知识

定义

数论函数 \(f(x)\)\(g(x)\) 的 Dirichlet 卷积,记作 \(f*g\),定义为数论函数:

\[(f*g)(n)=\sum_{d|n}f(d)g(\cfrac{n}{d})=\sum_{xy=d}f(x)g(y) \]

以下给出一些基本函数:

  • 幂函数 \(id_t\)\(id_t(n)=n^t\)
  • 元函数 \(\varepsilon\)\(\varepsilon(n)=[n=1]\)
  • 恒等函数 \(1\)\(1(n)=1\),也就是 \(id_0\)
  • \(id\)\(id(n)=n\),也就是 \(id_1\)
  • 莫比乌斯函数 \(\mu\)
  • 欧拉函数 \(\varphi\)

性质

Dirichlet 卷积的代数性质非常优秀:

  • 交换律\(f*g=g*f\)

    证明:由于一个数 \(n\) 的因数总是成对出现,根据定义式,得证。

  • 结合律\((f*g)*h=f*(g*h)\)

    证明:展开左式得:

    \[((f*g)*h)(n)=\sum_{xyz=n}f(x)g(y)h(z)=(f*(g*h))(n) \]

    得证。

  • 分配率:\((f+g)*h=f*h+g*h\)

    证明:展开左式得:

    \[((f+g)*h)(n)=\sum_{xy=n}(f(x)+g(x))h(y)=\sum_{xy=n}f(x)h(y)+\sum_{xy=n}g(x)h(y)=f*h+g*h \]

  • 单位元:\(f*\varepsilon=\varepsilon*f=f\),其中 \(\varepsilon(n)=[n=1]\) 为卷积的单位元。

常见公式

利用常见的公式,可以让原函数变形,从而求解复杂问题:

  • 莫比乌斯函数的核心性质 \(\mu*1=\varepsilon\)

    证明:本质含义: \(n\) 的所有约数的莫比乌斯函数和,只有当 \(n=1\) 时为 \(1\),其余为 \(0\)。证明详见莫比乌斯函数

  • 欧拉函数的核心性质 \(\varphi*1=id\)

    证明:这个也算老熟人了,本质为:\(\sum\limits_{d|n}\varphi(d)=n\)。详见欧拉函数

  • 另外,还有带权的基本函数,如 \((id\cdot \mu)*id=\varepsilon\),也就是 \(f(n)=n\cdot \mu(u)\)

    证明:将左式展开:

    \[\sum_{d|n}(id\cdot \mu)(d)\cdot\cfrac{n}{d}=\sum_{d|n}d\cdot \mu(d)\cdot(\cfrac{n}{d}) \]

    合并相消:

    \[n\sum_{d|n}\mu(d)=n\cdot\varepsilon(n)=\varepsilon(n) \]

  • 带权欧拉函数同理: \((id\cdot \varphi)*id=id_2\)

杜教筛

前置知识:数论分块(似乎卷积几乎都会使用)。

杜教筛是用与处理积性函数的前缀和的方法,它可以在低于线性时间复杂度内处理 \(S_f(n)=\sum\limits^{n}_{i=1}f(i)\)

公式推导

实际上,杜教筛的本质是通过构造函数 \(g\)\(h\),使得 \(f*g=h\) 成立,再依据 Dirichlet 卷积变形求解前缀和。

现在进行推导:

\[f*g=h \]

对两侧同求前缀和:

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

按 Dirichlet 卷积展开:

\[\sum_{i=1}^{n}\sum_{d|i}f(d)g(\cfrac{i}{d})=\sum_{i=1}^nh(i) \]

交换枚举顺序(数论常用变形方式):

\[\sum_{d=1}^ng(d)\sum_{i=1}^{\lfloor \frac{n}{d}\rfloor}f(i)=\sum_{i=1}^nh(i) \]

可以发现,\(\sum\limits_{i=1}^{\lfloor \frac{n}{d}\rfloor}f(i)\) 可以表示 \(f\) 的前缀和,以下记作 \(S_f(\lfloor \cfrac{n}{d}\rfloor)\)

\[\sum_{d=1}^ng(d)S_f(\lfloor \cfrac{n}{d}\rfloor)=\sum_{i=1}^nh(i) \]

我们想要求的是 \(S_f(n)\),注意到当 \(d=1\)时,\(\lfloor \cfrac{n}{d}\rfloor=1\),将这一项提出来:

\[g(1)S_f(n)+\sum_{d=2}^ng(d)S_f(\lfloor \cfrac{n}{d}\rfloor)=\sum_{i=1}^nh(i) \]

移相得:

\[g(1)S_f(n)=\sum_{i=1}^nh(i)-\sum_{d=2}^ng(d)S_f(\lfloor \cfrac{n}{d}\rfloor) \]

我们成功将问题规模缩小了。观察右式,\(h\)\(g\) 一般选择能够快速求解的函数,而 \(S_f(\lfloor \cfrac{n}{d}\rfloor)\) 则可通过数论分块递归为子问题的方式求解。

构造技巧

对于未知性质的积性函数 \(f\),我们无法直接想出 \(g\)\(h\),但能够通过一些方法凑出来

根据 Dirichlet 卷积的定义可知:、

\[(f*g)(p)=f(1)g(p)+f(p)g(1) \]

又因为积性函数通用性质 \(f(1)=g(1)=1\)

所以:

\[h(p)=(f*g)(p)=f(p)+g(p) \]

即:

\[f+g=h \]

我们只需要将 \(f\) 拆分成 \(h - g\) 的形式即可。(注意 \(h\)\(g\) 一般为数论的基本函数)

例:当 \(f(n)=n\cdot\varphi(n)\),时,构造 \(g\)\(h\) 使得 \(f*g=h\)

  • 代入质数 \(p\),得:\(f(p)=p\cdot\varphi(p)=p(p-1)=p^2-p\)
  • 显然可得 \(g=p=id\)\(h=p^2=id_2\)

注意有些题目会挖坑,需代入 \(p\)\(p^2\) 验证。

例题

莫比乌斯函数前缀和

根据 \(\mu*1=\varepsilon\),构造出 \(f * g=h\) 的形式,套用公式:

\[S_{\mu}(n)=1-\sum_{d=1}^nS_{\mu}(\lfloor\cfrac{n}{d}\rfloor) \]

欧拉函数前缀和

根据 \(\varphi*1=id\),构造出 \(f * g=h\) 的形式,套用公式:

\[S_{\varphi}(n)=\cfrac{n(1+n)}{2}-\sum_{d=2}^nS_{\varphi}(\lfloor\cfrac{n}{d}\rfloor) \]

代码(洛谷杜教筛模板):

注意:需使用记忆化搜索。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e7;
// 线性筛
int prime[maxn + 5],book[maxn + 5],pcnt = 0;
ll mu[maxn + 5],phi[maxn + 5];
ll Smu[maxn + 5],Sphi[maxn + 5];
void init() {
  for (int i = 1;i <= maxn;i ++) book[i] = 1;
  book[0] = book[1] = 0;
  mu[1] = phi[1] = 1ll;
  for (int i = 2;i <= maxn;i ++) {
    if (book[i] == 1) {
      prime[++ pcnt] = i;
      mu[i] = -1,phi[i] = i - 1;
    }
    for (int j = 1;j <= pcnt && i * prime[j] <= maxn;j ++) {
      book[i * prime[j]] = 0;
      if (i % prime[j] == 0) { // p 为 i的最小质因子
        mu[i * prime[j]] = 0;
        phi[i * prime[j]] = phi[i] * prime[j];
        break;
      } else {
        mu[i * prime[j]] = -mu[i];
        phi[i * prime[j]] = phi[i] * (prime[j] - 1);
      }
    }
  }
  Smu[0] = Sphi[0] = 0;
  for (int i = 1;i <= maxn;i ++) {
    Smu[i] = Smu[i - 1] + mu[i];
    Sphi[i] = Sphi[i - 1] + phi[i];
  }
}
unordered_map<ll,ll> bookmu,bookphi;
ll get_Smu(ll n) {
  if (n <= maxn) return Smu[n];
  if (bookmu.count(n)) return bookmu[n];
  // 数论分块
  ll sum = 0ll;
  for (ll l = 2,r = 0;l <= n;l = r + 1) {
    r = (n / (n / l));
    sum += 1ll * get_Smu(n / l) * (r - l + 1);
  }
  return bookmu[n] = 1ll - sum;
}
ll get_Sphi(ll n) {
  if (n <= maxn) return Sphi[n];
  if (bookphi.count(n)) return bookphi[n];
  // 数论分块
  ll sum = 0ll;
  for (ll l = 2,r = 0;l <= n;l = r + 1) {
    r = (n / (n / l));
    sum += 1ll * get_Sphi(n / l) * (r - l + 1);
  }
  return bookphi[n] = (1ll * n * (n + 1) / 2) - sum;
}
int T;
ll n;
int main() {
  ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);

  init();
  cin >> T;
  while (T --) {
    cin >> n;
    cout << get_Sphi(n) << " " << get_Smu(n) << "\n";
  }
  return 0;
}

洛谷P3768 简单的数学题

输入一个整数 \(n\) 和一个整数 \(p\),你需要求出:

\[\left(\sum_{i=1}^n\sum_{j=1}^n ij \gcd(i,j)\right) \bmod p \]

现对原式进行化简:

先转化成常见形式(化为 \([gcd(i,j)=1]\)\(\mu\) 的形式):

\[\sum_{d=1}^{n}d^3\sum_{x=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{y=1}^{\lfloor\frac{n}{d}\rfloor}xy[gcd(x,y)=1] \]

\[\sum_{d=1}^{n}d^3\sum_{x=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{y=1}^{\lfloor\frac{n}{d}\rfloor}xy\sum_{t|gcd(x,y)}\mu(t) \]

交换枚举顺序:

\[\sum_{d=1}^{n}d^3\sum_{t=1}^{\lfloor\frac{n}{d}\rfloor}t^2\mu(t)\sum_{x=1}^{\lfloor\frac{n}{dt}\rfloor}\sum_{y=1}^{\lfloor\frac{n}{dt}\rfloor}xy \]

显然后面是常数(注:\(S_f\) 表示 \(f\) 的前缀和):

\[\sum_{d=1}^{n}d^3\sum_{t=1}^{\lfloor\frac{n}{d}\rfloor}t^2\mu(t)S_{id_3}(\cfrac{n}{dt}) \]

最巧妙的一步来了,令 \(T = dt\),易知 \(1\le T\le n\),在外层枚举 \(T\)

\[\sum_{T=1}^nS_{id_3}(\lfloor\cfrac{n}{T}\rfloor)\sum_{t|T}t^2\mu(t)\cdot(\cfrac{T}{t})^3 \]

化简消元:

\[\sum_{T=1}^nS_{id_3}(\lfloor\cfrac{n}{T}\rfloor)\sum_{t|T}\mu(t)\cdot\cfrac{T^3}{t} \]

可以发现是一个 \(f(t)\cdot g(\cfrac{C}{t})\) 的形式,考虑将右侧转化为 Dirichlet 卷积:

\[\sum_{T=1}^nT^2S_{id_3}(\lfloor\cfrac{n}{T}\rfloor)\sum_{t|T}\mu(t)\cdot id(\cfrac{T}{t}) \]

即:

\[\sum_{T=1}^nT^2\cdot S_{id_3}(\lfloor\cfrac{n}{T}\rfloor)\cdot(\mu*id)(T) \]

通过常见公式,我们知道 \(\varphi*1=id\),两侧同乘 \(\mu\),可得:

\[\mu * id=\varphi \]

代入原式就转化为了:

\[\sum_{T=1}^nT^2\cdot S_{id_3}(\lfloor\cfrac{n}{T}\rfloor)\cdot\varphi(T) \]

只要能够求得 \(S_f(n)=\sum\limits_{T=1}^nT^2\cdot\varphi(T)\) 就能够利用数论分块解决了。

现在使用杜教筛,但如何构造 \(g\)\(h\)

依据前面的方法,代入质数 \(p\),则有:

\[f(p)=p^2\cdot\varphi(p)=p^2(p-1)=p^3-p^2 \]

\(f+g=h\) 得:

\[p^3-p^2+g=h \]

易得:\(g=p^2=id_2,h=p^3=id_3\)

套用杜教筛公式:

\[S_f(n)=\sum_{i=1}^{n}id_3(i)-\sum_{d=2}^{n}id_2(d)S_f(\lfloor\cfrac{n}{d}\rfloor) \]

代码:注意 maxn 的选择。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 5e6; // 注意 1e6:TLE 1e7:MLE
ll mod = 0;
ll inv(ll x) {
  ll res = 1ll,p = mod - 2ll;
  while (p) {
    if (p & 1) res = res * x % mod;
    p >>= 1,x = x * x % mod;
  }
  return res;
}
ll inv2 = 0,inv6 = 0;
int book[maxn + 5],prime[maxn + 5],pcnt = 0;
ll phi[maxn + 5];
ll sum[maxn + 5];
void init() {
  for (int i = 1;i <= maxn;i ++) book[i] = 1; 
  phi[1] = 1,book[0] = book[1] = 0;
  for (int i = 2;i <= maxn;i ++) {
    if (book[i]) {
      prime[++ pcnt] = i;
      phi[i] = i - 1;
    }
    for (int j = 1;j <= pcnt && i * prime[j] <= maxn;j ++) {
      int p = prime[j];
      book[i * p] = 0;
      if (i % p == 0) {
        phi[i * p] = phi[i] * p;
        break;
      } else phi[i * p] = phi[i] * (p - 1);
    }
  }
  sum[0] = 0ll;
  for (ll i = 1;i <= (ll)maxn;i ++) {
    sum[i] = sum[i - 1] + (i * i % mod) * phi[i] % mod;
    sum[i] %= mod;
  }
}
ll Sid2(ll n) {
  n %= mod;
  return n * (n + 1) % mod * (2 * n + 1) % mod * inv6 % mod;
}
ll Sid3(ll n) {
  n %= mod;
  ll res = (n * (n + 1) % mod) * inv2 % mod;
  return res * res % mod;
}
unordered_map<ll,ll> mp;
ll get_sum(ll n) {
  if (n <= maxn) return sum[n];
  if (mp.count(n)) return mp[n];
  ll res = 0ll;
  for (ll l = 2ll,r;l <= n;l = r + 1ll) {
    r = (n / (n / l));
    ll t = (Sid2(r) - Sid2(l - 1) + mod) % mod;
    res += (get_sum(n / l) * t) % mod;
    res %= mod;
  }
  res = (Sid3(n) - res + mod) % mod;
  return mp[n] = res % mod;
}
int main() {
  ll n; cin >> mod >> n;
  init(); inv2 = inv(2),inv6 = inv(6);
  ll ans = 0;
  for (ll l = 1ll,r;l <= n;l = r + 1ll) {
    r = (n / (n / l));
    ans += Sid3(n / l) * ((get_sum(r) - get_sum(l - 1) + mod) % mod) % mod;
    ans %= mod;
  }
  cout << ans;
  return 0;
}
posted @ 2026-07-19 18:02  SilverWolf_lhh  阅读(4)  评论(0)    收藏  举报