『学习笔记』浅谈莫比乌斯反演

莫比乌斯反演是数论中的重要内容。对于一些函数 \(f(n)\),如果很难直接求出它的值,而容易求出其倍数和或约数和 \(g(n)\),那么可以通过莫比乌斯反演简化运算,求得 \(f(n)\) 的值。——OI-wiki

可见莫反的强大。

前置知识:数论分块

数论分块可以快速计算一些含有除法向下取整的和式,如 \(\sum\limits_{i=1}^n f(i)\times g\left(\left\lfloor\dfrac{n}{i}\right\rfloor\right)\)。由于 \(y=\left\lfloor\dfrac nx\right\rfloor\) 的图像是成阶梯式的变化,便对于一段满足任意两个 \(i,j\) 都满足 \(\left\lfloor\dfrac ni\right\rfloor=\left\lfloor\dfrac nj\right\rfloor\)的区间 \([l,r]\),其对应的答案为 \(g\left(\left\lfloor\dfrac ni\right\rfloor\right)\times\sum\limits_{i=l}^rf(i)\),后面的便可以提前预处理出。

但是这个 \([l,r]\) 怎么求呢?

我们令 \(k=\left\lfloor\dfrac nl\right\rfloor\),则有 \(k\le\dfrac nl\)。从而 \(\left\lfloor\dfrac nk\right\rfloor\ge\left\lfloor\dfrac n{\frac nl}\right\rfloor=l\),得 \(r\le\left\lfloor\frac n{\left\lfloor\frac nl\right\rfloor}\right\rfloor\),即 \(r_{\max}=\left\lfloor\frac n{\left\lfloor\frac nl\right\rfloor}\right\rfloor\)

例题:UVa11526 H(n)

题意:求

\[\sum_{i=1}^n\left\lfloor\dfrac ni\right\rfloor \]

板子题,代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int n, ans = 0, l = 1;
  cin >> n;
  while(l <= n) {
    int r = n / (n / l);
    ans += (r - l + 1) * (n / l);
    l = r + 1;
  }
  cout << ans;
  return 0;
}

数论分块一般配合莫反进一步降低时间复杂度。

习题

  1. P2261 [CQOI2007]余数求和
  2. P2260 [清华集训2012]模积和
  3. P3935 Calculating

定义

\(\mu\) 为莫比乌斯函数,定义为

\[\mu(n)= \begin{cases} 1&n=1\\ 0&n\text{ 含有平方因子}\\ (-1)^k&k\text{ 为 }n\text{ 的本质不同质因子个数}\\ \end{cases} \]

\(n=\prod\limits^k_{i=1}p_i^{c_i}\)

  1. \(n=1\)\(\mu(n)=1\)
  2. 否则:
    1. \([c_i>1]\neq0\),则存在 $p_i^2\mid n $,此时 \(\mu(n)=0\)
    2. 否则每个质因子都只出现过一次,此时 \(\mu(n)=(-1)^k\)

性质

首先,莫比乌斯函数为积性函数,并且满足

\[\sum\limits_{d\mid n}\mu(d)=\begin{cases} 1&n=1\\ 0&\text{else}\\ \end{cases} \]

接着给一下反演的结论:\([\gcd(i,j)=1]=\sum\limits_{d\mid\gcd(i,j)}\mu(d)\)

求法

因为莫比乌斯函数为积性函数,所以可用线性筛来求解。

代码如下:

inline void getMu() {
  mu[1] = 1;
  for(int i = 2; i <= n; ++i) {
    if(!vis[i]) {
      p[++tot] = i;
      mu[i] = -1;
    }
    for(int j = 1; j <= tot && i * p[j] <= n; ++j) {
      vis[i * p[j]] = 1;
      if(i % p[j] == 0) {
        mu[i * p[j]] = 0;
        break;
      }
      mu[i * p[j]] = -mu[i];
    }
  }
}

变换

\(f(x),g(x)\) 为两个数论函数。

形式 \(1\):若 \(f(n)=\sum\limits_{d\mid n}g(d)\),则 \(g(n)=\sum\limits_{d\mid n}\mu(d)f\left(\frac nd\right)\)

形式 \(2\):若 \(f(n)=\sum\limits_{n\mid d}g(d)\),则 \(g(n)=\sum\limits_{n|d}\mu\left(\frac dn\right)f(d)\)

这时,我们称 \(f(x)\)\(g(x)\) 的莫比乌斯变换,\(g(x)\)\(f(x)\) 的莫比乌斯逆变换(反演)。

例题

eg1. P2522 [HAOI2011] Problem b

很经典的一题,求:

\[\sum_{i=a}^b\sum_{j=c}^d [\gcd(i,j)=k] \]

考虑前缀和,对于 \((a,b,c,d)=(1,1,n,m)\),进行变换。

\[\sum_{i=1}^n\sum_{j=1}^m [\gcd(i,j)=k] \]

\(\gcd(i,j)=k\),则 \(\gcd(i\div k,j\div k)=1\),所以化为:

\[\sum_{i=1}^{\left\lfloor\frac nk\right\rfloor}\sum_{j=1}^{\left\lfloor\frac mk\right\rfloor} [\gcd(i,j)=1] \]

\([\gcd(i,j)=1]\) 可通过反演结论化为 \(\sum\limits_{d\mid\gcd(i,j)}\mu(d)\)

\[\sum_{i=1}^{\left\lfloor\frac nk\right\rfloor}\sum_{j=1}^{\left\lfloor\frac mk\right\rfloor}\sum_{d\mid\gcd(i,j)}\mu(d) \]

变换求和顺序,得:

\[\left(\sum_{d=1}^{\min\left\{\left\lfloor\frac nk\right\rfloor,\left\lfloor\frac mk\right\rfloor\right\}}\mu(d)\right)\left(\sum_{i=1}^{\left\lfloor\frac nk\right\rfloor}[d\mid i]\right)\left(\sum_{j=1}^{\left\lfloor\frac mk\right\rfloor}[d\mid j]\right) \]

继续变化得:

\[\sum_{d=1}^{\min\left\{\left\lfloor\frac nk\right\rfloor,\left\lfloor\frac mk\right\rfloor\right\}}\mu(d)\cdot\left\lfloor\dfrac n{kd}\right\rfloor\cdot\left\lfloor\dfrac m{kd}\right\rfloor \]

显然可以用数论分块求,总时间复杂度为 \(\mathcal{O}(n+t\sqrt{n})\)

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e4 + 5;
int n, k, p[N], tot, f[N], sum[N];
bool vis[N];
inline int s(int a, int b) {
  a /= k, b /= k;
  int l = 1, ans = 0;
  while(l <= min(a, b)) {
    int r = min(a / (a / l), b / (b / l));
    ans += (sum[r] - sum[l - 1]) * (a / l) * (b / l);
    l = r + 1;
  }
  return ans;
}
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  f[1] = vis[1] = 1;
  for(int i = 2; i < N; i++) {
    if(!vis[i]) {
      p[++tot] = i;
      f[i] = -1;
    }
    vis[i] = 1;
    for(int j = 1; j <= tot && i * p[j] < N; j++) {
      vis[i * p[j]] = 1;
      if(i % p[j] == 0) {
        f[i * p[j]] = 0;
        break;
      }
      f[i * p[j]] = -f[i];
    }
  }
  for(int i = 1; i < N; i++) {
    sum[i] = sum[i - 1] + f[i];
  }
  cin >> n;
  while(n--) {
    int a, b, c, d;
    cin >> a >> b >> c >> d >> k;
    cout << s(b, d) - s(a - 1, d) - s(b, c - 1) + s(a - 1, c - 1) << '\n';
  }
  return 0;
}

*注:本体还有一个弱化版,link

eg2. P1891 疯狂 LCM

求:

\[\sum_{i=1}^n\operatorname{lcm}(i,n) \]

不难想到转换为

\[\sum_{i=1}^n\dfrac{i\cdot n}{\gcd(i,n)} \]

首尾配对得:

\[\frac12\left(\sum_{i=1}^{n-1}\dfrac{i\cdot n}{\gcd(i,n)}+\sum_{i=n-1}^1\dfrac{i\cdot n}{\gcd(i,n)}\right)+n \]

通过辗转相减法得:

\[\frac12\left(\sum_{i=1}^{n-1}\dfrac{i\cdot n}{\gcd(i,n)}+\sum_{i=n-1}^1\dfrac{i\cdot n}{\gcd(n-i,n)}\right)+n \]

再合并得:

\[\frac12\sum_{i=1}^{n-1}\dfrac{n^2}{\gcd(i,n)}+n=\frac12\sum_{i=1}^n\dfrac{n^2}{\gcd(i,n)}+\frac12n \]

\(\gcd(i,n)=d\) 的个数等于 \(\gcd\left(\dfrac id,\dfrac nd\right)=1\) 的个数等于 \(\varphi\left(\dfrac nd\right)\),于是进一步转换为:

\[\frac 12\sum_{d\mid n}\dfrac{n^2\cdot \varphi\left(\frac nd\right)}d+\frac12n=\frac12n\cdot\left(\sum_{d|n}d\cdot\varphi(d)+1\right)=\frac12n\cdot\left(g(n)+1\right) \]

所以 \(g(n)\) 为积性函数,考虑用线性筛求出。

由于本人实力有限,便只给出结论:

\[g(i\cdot p_j)=g(i)+\left(g(i)-g(\dfrac{i}{p_j})\right)\cdot p_j^2 \]

线性筛部分代码:

for(int i = 2; i < N; i++) {
  if(!vis[i]) {
    p[++tot] = i;
    f[i] = i * (i - 1) + 1;
  }
  for(int j = 1; j <= tot && i * p[j] < N; j++) {
    vis[i * p[j]] = 1;
    if(i % p[j] == 0) {
      f[i * p[j]] = f[i] + (f[i] - f[i / p[j]]) * p[j] * p[j];
      break;
    }
    f[i * p[j]] = f[i] * f[p[j]];
  }
}

总时间复杂度为 \(\mathcal{O}(n+t)\)

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 5;
int n, p[N], tot, f[N];
bool vis[N];
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  f[1] = 1;
  for(int i = 2; i < N; i++) {
    if(!vis[i]) {
      p[++tot] = i;
      f[i] = i * (i - 1) + 1;
    }
    for(int j = 1; j <= tot && i * p[j] < N; j++) {
      vis[i * p[j]] = 1;
      if(i % p[j] == 0) {
        f[i * p[j]] = f[i] + (f[i] - f[i / p[j]]) * p[j] * p[j];
        break;
      }
      f[i * p[j]] = f[i] * f[p[j]];
    }
  }
  int t;
  cin >> t;
  while(t--) {
    int n;
    cin >> n;
    cout << (f[n] + 1) * n / 2 << '\n';
  }
  return 0;
}

eg3. SP26017 GCDMAT - GCD OF MATRIX

求:

\[\sum_{i=i_1}^{i_2}\sum_{j=j_1}^{j_2}\gcd(i,j)\bmod(10^9+7) \]

同样考虑前缀和,即求 \(\sum\limits_{i=1}^n\sum\limits_{j=1}^m\gcd(i,j)\)

变换(令 \(n\le m\)):

\[\sum_{i=1}^n\sum_{j=1}^m\sum_{d=1}^n[\gcd(i,j)=d]\\=\sum_{d=1}^nd\cdot\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=d]\\=\sum_{d=1}^nd\cdot\sum_{i=1}^{\left\lfloor\frac nd\right\rfloor}\sum_{j=1}^{\left\lfloor\frac md\right\rfloor}[\gcd(i,j)=1] \]

反演结论:

\[\sum_{d=1}^nd\cdot\sum_{i=1}^{\left\lfloor\frac nd\right\rfloor}\sum_{j=1}^{\left\lfloor\frac md\right\rfloor}\sum_{p\mid\gcd(i,j)}\mu(p) \]

枚举 \(p\)

\[\sum_{d=1}^nd\cdot\sum_{p=1}^{\left\lfloor\frac nd\right\rfloor}\sum_{i=1}^{\left\lfloor\frac n{pd}\right\rfloor}\sum_{j=1}^{\left\lfloor\frac m{[d}\right\rfloor}\mu(p)\\=\sum_{d=1}^n\sum_{p=1}^{\left\lfloor\frac nd\right\rfloor}\left\lfloor\dfrac n{pd}\right\rfloor\left\lfloor\dfrac m{pd}\right\rfloor d\cdot\mu(p) \]

再令 \(q=pd\),化为:

\[\sum_{d=1}^n\sum_{p=1}^{\left\lfloor\frac nd\right\rfloor}\left\lfloor\dfrac nq\right\rfloor\left\lfloor\dfrac mq\right\rfloor\dfrac qp\cdot\mu(p)\\=\sum_{q=1}^n\sum_{p\mid q}\left\lfloor\dfrac nq\right\rfloor\left\lfloor\dfrac mq\right\rfloor\dfrac qp\cdot\mu(p) \]

\(\sum\limits_{p\mid q}\dfrac qp\cdot\mu(p)=\sum\limits_{i=1}^q\varphi(i)\) 可用线性筛求出,前面可用数论分块求解,总时间复杂度为 \(\mathcal{O}(n+T\sqrt n)\)

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e7 + 5, Mod = 1e9 + 7;
int n, m, p[N], tot, f[N], sum[N];
bool vis[N];
inline int s(int x, int y) {
  int l = 1, ans = 0;
  if(x > y) {
    swap(x, y);
  }
  while(l <= x) {
    int r = min(x / (x / l), y / (y / l));
    ans = (ans + (sum[r] - sum[l - 1]) * (x / l) % Mod * (y / l) % Mod) % Mod;
    l = r + 1;
  }
  return ans;
}
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  int t;
  cin >> t >> n >> m;
  f[1] = 1;
  for(int i = 2; i <= min(n, m); i++) {
    if(!vis[i]) {
      p[++tot] = i;
      f[i] = i - 1;
    }
    for(int j = 1; j <= tot && i * p[j] <= min(n, m); j++) {
      vis[i * p[j]] = 1;
      if(i % p[j] == 0) {
        f[i * p[j]] = f[i] * p[j] % Mod;
        break;
      }
      f[i * p[j]] = f[i] * (p[j] - 1) % Mod;
    }
  }
  for(int i = 1; i <= min(n, m); i++) {
    sum[i] = (sum[i - 1] + f[i]) % Mod;
  }
  while(t--) {
    int a, b, c, d;
    cin >> a >> b >> c >> d;
    cout << ((s(c, d) - s(a - 1, d) - s(c, b - 1) + s(a - 1, b - 1)) % Mod + Mod) % Mod << '\n';
  }
  return 0;
}

此题还有一个加强版,但本人不会卡常(doge,感兴趣的可以做一做,SP26045 GCDMAT2 - GCD OF MATRIX (hard)

eg4. P1829 [国家集训队] Crash的数字表格 / JZPTAB

求:

\[\sum_{i=1}^n\sum_{j=1}^m\operatorname{lcm}(i,j) \]

显然能化为:

\[\sum_{i=1}^n\sum_{j=1}^m\dfrac{i\cdot j}{\gcd(i,j)} \]

套路:

\[\sum_{i=1}^n\sum_{j=1}^m\sum_{d|\gcd(i,j),\gcd\left(\frac id,\frac jd\right)=1}\dfrac{i\cdot j}d \]

改变求和顺序:

\[\sum_{d=1}^nd\cdot\sum_{i=1}^{\left\lfloor\frac nd\right\rfloor}\sum_{j=1}^{\left\lfloor\frac md\right\rfloor}[\gcd(i,j)=1]i\cdot j \]

通过反演结论得(令 \(n'=\left\lfloor\dfrac nd\right\rfloor\le \left\lfloor\dfrac md\right\rfloor=m'\)):

\[\sum_{d=1}^nd\cdot\sum_{p=1}^{n'}\sum_{p\mid i}^{n'}\sum_{p\mid j}^{m'}\mu(p)\cdot ij \]

套路:

\[\sum_{d=1}^nd\cdot\sum_{p=1}^{n'}\mu(p)\cdot p^2\sum_{i=1}^{\left\lfloor\frac{n'}p\right\rfloor}\sum_{j=1}^{\left\lfloor\frac{m'}p\right\rfloor}i\cdot j\\=\sum_{d=1}^nd\cdot\sum_{p=1}^{\left\lfloor\frac nd\right\rfloor}\mu(p)\cdot p^2\cdot\frac{n'\cdot(n'+1)}2\times\frac{m'\cdot(m'+1)}2 \]

令后一段为 \(\operatorname{s}(n,m)\),从而原式等于

\[\sum_{d=1}^nd\cdot\operatorname{s}\left(\left\lfloor\frac nd\right\rfloor,\left\lfloor\frac md\right\rfloor\right) \]

\(\operatorname{s}(n,m)\) 内部的前半段可以线性筛预处理,后半段可用数论分块求解。

而整体的答案又可以再用一遍数论分块,注意其中细节比较多(尤其是取模)。

总时间复杂度为 \(\mathcal{O}(n+\sqrt{m}\cdot\sqrt{m})=\mathcal{O}(n+m)\)

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e7 + 5, Mod = 20101009;
int n, m, p[N], tot, f[N], sum[N];
bool vis[N];
inline int s(int x, int y) {
  int l = 1, ans = 0;
  while(l <= min(x, y)) {
    int r = min(x / (x / l), y / (y / l));
    int tmp = ((x / l) * (x / l + 1) / 2 % Mod) * ((y / l) * (y / l + 1) / 2 % Mod) % Mod;
    ans = (ans + (sum[r] - sum[l - 1] + Mod) % Mod * tmp % Mod) % Mod;
    l = r + 1;
  }
  return ans;
}
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n >> m;
  f[1] = 1;
  for(int i = 2; i < N; i++) {
    if(!vis[i]) {
      p[++tot] = i;
      f[i] = -1;
    }
    for(int j = 1; j <= tot && i * p[j] < N; j++) {
      vis[i * p[j]] = 1;
      if(i % p[j] == 0) {
        f[i * p[j]] = 0;
        break;
      }
      f[i * p[j]] = -f[i];
    }
  }
  for(int i = 1; i < N; i++) {
    sum[i] = (sum[i - 1] + i * i % Mod * (f[i] + Mod) % Mod) % Mod;
  }
  int l = 1, ans = 0;
  while(l <= min(n, m)) {
    int r = min(n / (n / l), m / (m / l));
    ans = (ans + (r - l + 1) * (l + r) / 2 % Mod * s(n / l, m / l) % Mod) % Mod;
    l = r + 1;
  }
  cout << ans;
  return 0;
}

eg5. P3327 [SDOI2015] 约数个数和

求:

\[\sum_{i=1}^n\sum_{j=1}^md(ij) \]

可化为:

\[\sum_{i=1}^n\sum_{j=1}^m\sum_{x\mid i}\sum_{y\mid i}[\gcd(p,q)=1] \]

下面化简 \(d(i,j)\)

\[\begin{aligned} d(i\cdot j) =&\sum_{x \mid i}\sum_{y \mid j}\sum_{p \mid \gcd(x,y)}\mu(p)\\ =&\sum_{p=1}^{\min(i,j)}\sum_{x \mid i}\sum_{y \mid j}[p \mid \gcd(x,y)]\cdot\mu(p)\\ =&\sum_{p \mid i,p \mid j}\mu(p)\sum_{x \mid i}\sum_{y \mid j}[p \mid \gcd(x,y)]\\ =&\sum_{p \mid i,p \mid j}\mu(p)\sum_{x \mid \frac{i}{p}}\sum_{y \mid \frac{j}{p}}1\\ =&\sum_{p \mid i,p \mid j}\mu(p)d\left(\frac{i}{p}\right)d\left(\frac{j}{p}\right)\\ \end{aligned} \]

再带回到原式得(令 \(n\le m\)\(S(n)=\sum\limits_{i=1}^nd(i)\)):

\[\begin{aligned} &\sum_{i=1}^n\sum_{j=1}^md(i\cdot j)\\ =&\sum_{i=1}^n\sum_{j=1}^m\sum_{p \mid i,p \mid j}\mu(p)d\left(\frac{i}{p}\right)d\left(\frac{j}{p}\right)\\ =&\sum_{p=1}^{n} \sum_{i=1}^n\sum_{j=1}^m [p \mid i,p \mid j]\cdot\mu(p)d\left(\frac{i}{p}\right)d\left(\frac{j}{p}\right)\\ =&\sum_{p=1}^{n} \sum_{i=1}^{\left\lfloor\frac{n}{p}\right\rfloor}\sum_{j=1}^{\left\lfloor\frac{m}{p}\right\rfloor} \mu(p)d(i)d(j)\\ =&\sum_{p=1}^{n}\mu(p) \sum_{i=1}^{\left\lfloor\frac{n}{p}\right\rfloor}d(i) \sum_{j=1}^{\left\lfloor\frac{m}{p}\right\rfloor}d(j)\\ =&\sum_{p=1}^{n}\mu(p) S\left(\left\lfloor\frac{n}{p}\right\rfloor\right) S\left(\left\lfloor\frac{m}{p}\right\rfloor\right) \end{aligned} \]

\(S(n)\)\(\mu(n)\) 可线性筛预处理,再加上数论分块,总时间复杂度为 \(\mathcal{O}(n+t\sqrt{n})\)

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 50005;
int T, p[N], tot, f[N], d[N], t[N];
bool vis[N];
inline int s(int n, int m) {
  int l = 1, ans = 0;
  if(n > m) {
    swap(n, m);
  }
  while(l <= n) {
    int r = min(n / (n / l), m / (m / l));
    ans += d[n / l] * d[m / l] * (f[r] - f[l - 1]);
    l = r + 1;
  }
  return ans;
}
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  f[1] = d[1] = 1;
  for(int i = 2; i < N; i++) {
    if(!vis[i]) {
      p[++tot] = i;
      f[i] = -1;
      d[i] = 2;
      t[i] = 1;
    }
    for(int j = 1; j <= tot && i * p[j] < N; j++) {
      vis[i * p[j]] = 1;
      if(i % p[j] == 0) {
        f[i * p[j]] = 0;
        d[i * p[j]] = d[i] / (t[i] + 1) * (t[i] + 2);
        t[i * p[j]] = t[i] + 1;
        break;
      }
      f[i * p[j]] = -f[i];
      d[i * p[j]] = d[i] << 1;
      t[i * p[j]] = 1;
    }
  }
  for(int i = 2; i < N; i++) {
    f[i] += f[i - 1];
    d[i] += d[i - 1];
  }
  cin >> T;
  while(T--) {
    int n, m;
    cin >> n >> m;
    cout << s(n, m) << '\n';
  }
  return 0;
}
posted @ 2024-01-10 21:13  cyf1208  阅读(104)  评论(1)    收藏  举报