『学习笔记』莫比乌斯反演
定义&证明
前置知识(定义)
- \([sth]=1/0\) ,当 \(sth\) 为真时即 \(1\) ,否则为 \(0\)
- \(I(n)=1\) ,恒等函数,是完全积性函数
- \(e(n)=[n=1]\) ,元函数,是完全积性函数
- \(id(n)=n\) ,单位函数,是完全积性函数
- \(\varphi(n)=\sum\limits_{i=1}^n[gcd(i,n)=1]\) ,欧拉函数,是积性函数
- \(\mu(n)\) ,莫比乌斯函数,是积性函数
- 两个函数的狄利克雷卷积写作 \(f*g\) , \((f*g)(n)=\sum\limits_{d\mid n}f(d)g(n/d)\)
莫比乌斯函数的定义
假设有一个函数 \(F(x)=\sum\limits_{d\mid x}f(d)\) ,这个 \(F\) 很好求,但是怎么求 \(f\) 呢?
这就需要我们伟大的莫比乌斯反演了
很容易看出来上面这个式子就是个狄利克雷卷积的形式,即 \(F=f*I\)
于是我们把这个 \(I\) 放到左式,变成 \(F*I^{-1}=f\) , \(I^{-1}\) 叫做 \(I\) 在狄利克雷卷积意义下的逆,一个函数的逆和这个函数的狄利克雷卷积为元函数 \(e\)
注意: \(I(x)=1\) ,\(I\) 是恒等函数的意思
于是我们的莫比乌斯函数诞生了
莫比乌斯函数推导
然后我们易知 \(\mu\) 是个积性函数,因为 \(I\) 也是个积性函数
我们开始研究 \(\mu(p^k),p\in \mathbb{P},k\in \mathbb{N}\) 的情况
- \(k=0\) 时,显然 \(\mu(1)=1\)
- \(k=1\) 时,显然 \(\mu(p)=-1\) (由 \(\sum\limits_{d\mid p}\mu(d)I(\frac{p}{d})=e(p)\) 可得,显然 \(d\) 只能取 \(1,p\) )
- \(k\ge 2\) 时,\(\sum\limits_{d\mid p^k}\mu(d)I(\frac{p^k}{d})=e(p^k)=0\),所有的 \(I\) 都为 \(1\) ,即 \(\sum\limits_{d\mid p^k}\mu(d)=\sum\limits_{t=0}^{k}p^t=0\) ,考虑 \(k=2\) 时 \(\mu(p^2)=0\) , \(k=3,4,...\) 时 \(\mu(p^k)\) 仍然为 \(0\)
于是我们 \(\mu\) 的终极形态要出来了:\(\mu(n=\prod\limits_{i=1}^{m}p_i^{k_i})\) 呢?
由于 \(\mu\) 是积性函数,所以有:
显然 \(k_i\ge 2\) 的时候 \(\mu(p_i^{k_i})=0\)
于是当且仅当 \(k_i=1\) 的时候它才不为 \(0\),所以答案即为 \(\mu(n)=\prod\limits_{i=1}^m\mu(p_i)=(-1)^m\)
于是我们 \(\mu\) 的定义便浮出水面:
\(m\) 为 \(n\) 质因子种类数
莫比乌斯反演式
嵌入式
由 \(\mu\) 的定义得知 \(\mu * I=e\)
这个式子就很香,这叫嵌入式莫比乌斯反演
约数式
回到开头那个式子?
是不是很爽?
似乎还有一种倍数式,但我不会
线性筛莫比乌斯函数
这个太简单了,不讲(
void mobius() {
mu[1] = 1;
for (int i = 2; i <= maxn; i++) {
if (!v[i]) p[++tot] = i, mu[i] = -1;
for (int j = 1; j <= tot && i <= maxn / p[j]; j++) {
v[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
}
整除分块
不会的就去看 command_block 的博客 吧
例题
简单题
- 求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(i,j)=1]\) (以下 \(gcd(i,j)\) 均用 \((i,j)\) 表示)
考虑嵌入式莫比乌斯反演, \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[(i,j)=1]=\sum\limits_{i=1}^n\sum\limits_{j=1}^n\sum\limits_{d\mid (i,j)}\mu(d)=\sum\limits_{d=1}^{n}\mu(d)\sum\limits_{d\mid i}^{n}\sum\limits_{d\mid j}^{n}1\)
然后这个 \(\sum\limits_{d=1}^{n}\mu(d)\sum\limits_{d\mid i}^{n}\sum\limits_{d\mid j}^{n}1\) 显然是\(=\sum\limits_{d=1}^{n}\mu(d)(\sum\limits_{d\mid i}^{n}1)(\sum\limits_{d\mid j}^{n}1)=\sum\limits_{d=1}^{n}\mu(d)(n/d)(m/d)\)
于是就可以用整除分块+前缀和 \(O(\sqrt{n})\) 解决
int calc(int n) {
int l = 1, r = 0, res = 0;
while (l <= n) {
r = n / (n / l);
res += (n / l) * (n / l) * (sum[r] - sum[l - 1]);
l = r + 1;
}
return res;
}
「POI2007」ZAP-Queries
- \(\sum\limits_{i=1}^n\sum\limits_{j=1}^m[(i,j)=k]\) ?
把 \(k\) 除过去:
然后...就做完了
「NOI2010」能量采集
- 不难看出这个式子就是 \(2\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}(i,j)-nm\)
于是我们考虑这个式子 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}(i,j)\)
\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}(i,j)=\sum\limits_{k=1}^nk\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[(i,j)=k]\) ,于是此时复杂度就到 \(O(n\sqrt{n})\) ,可以通过本题
不是吧,NOI就这
「SDOI2015」约数个数和
- 求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}d(ij)\)
我们发现一件事:
改变一下求和顺序
于是我们可以设:
则
由于 \(g(x)\) 可以通过整除分块预处理出,总复杂度为 \(O(t\sqrt{n})\)
「MCOI-02」Convex Hull
求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\tau(i)\tau(j)\tau(\gcd(i,j))\)。
根据套路把 \(\gcd\) 提出来枚举:
(上一步是把这个 \(k\) 放到前面枚举了。)
然后大力设 \(t=dk\),那么 \(k=\frac{t}{d}\),我们去枚举这个 \(t\)。
(上一步是把 \(t\) 丢到式子的最前面了。)
然后呢?我们发现 \(\sum\limits_{d\mid t}^{n}\tau(d)\mu(\frac{t}{d})\) 是一个狄利克雷卷积的形式,并且 \(\tau * \mu=I\) ,也就是这个式子恒等于 \(1\)。
为啥?
因为由 \(\tau\) 的定义可得 \(\tau=I*I\) ,由于 \(\mu=I^{-1}\) ,所以把右式的 \(I\) 搞一个过去,就变成了 \(\tau*\mu=I\)。
这个 \(\tau\) 可以用线性筛在 \(O(n)\) 的复杂度内预处理出来,然后记录 \(\tau(it)\) 和 \(\tau(jt)\) 分别对每一个 \(t\) 的贡献,最后枚举这个 \(t\) 的时候乘起来就行了。
跑起来慢的要死,可能要多提交几遍。听说可以优化,但我不会 \(\mathtt{dirichlet}\) 前缀和啊。
#include <bits/stdc++.h>
#define int long long
namespace mystd {
inline int read() {
int w = 1, q = 0;
char ch = ' ';
while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
if (ch == '-') w = -1, ch = getchar();
while (ch >= '0' && ch <= '9') q = q * 10 + ch - '0', ch = getchar();
return w * q;
}
inline void write(int x) {
if (x < 0) {
x = ~(x - 1);
putchar('-');
}
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace std;
using namespace mystd;
const int maxn = 2e6 + 200;
int n, m, p, ans, s1[maxn], s2[maxn];
int f[maxn], d[maxn], vis[maxn], pr[maxn], tot;
void init() {
d[1] = f[1] = vis[1] = 1;
for (int i = 2; i <= maxn - 1; i++) {
if (!vis[i]) {
pr[++tot] = i;
f[i] = 1;
d[i] = 2;
}
for (int j = 1; j <= tot && i <= maxn / pr[j]; j++) {
vis[i * pr[j]] = 1;
if (i % pr[j] == 0) {
f[i * pr[j]] = f[i] + 1;
d[i * pr[j]] = d[i] * (f[i * pr[j]] + 1) / (f[i] + 1);
break;
}
f[i * pr[j]] = 1;
d[i * pr[j]] = d[i] * d[pr[j]];
}
}
}
signed main() {
init();
n = read();
m = read();
p = read();
if (n > m) swap(n, m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n / i; j++) {
s1[i] = (s1[i] + d[i * j]) % p;
}
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= m / i; j++) {
s2[i] = (s2[i] + d[i * j]) % p;
}
}
for (int i = 1; i <= n; i++) {
ans = (ans + s1[i] * s2[i] % p) % p;
}
write(ans);
return 0;
}
updated 2022/2/5 : 尽管我现在会了狄利克雷前缀和,我还是不会优化/doge