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

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

可见莫反的强大。

1|0前置知识:数论分块

数论分块可以快速计算一些含有除法向下取整的和式,如 \(\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\)

1|1例题: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|2习题

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

2|0定义

\(\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\)

3|0性质

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

\[\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)\)

4|0求法

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

代码如下:

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]; } } }

5|0变换

\(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)\) 的莫比乌斯逆变换(反演)。

6|0例题

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; }

__EOF__

本文作者cyf1208
本文链接https://www.cnblogs.com/cyf1208/p/17957333.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   cyf1208  阅读(59)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示