莫比乌斯反演笔记
本文同步发表于我的 洛谷博客。
1. 莫比乌斯函数
设正整数 的标准分解为 。
莫比乌斯函数定义为
重要结论
。该结论可以用莫比乌斯函数性质证明。
重要结论 莫比乌斯函数是积性函数,即对于 ,有 。
2. 狄利克雷卷积
1. 一些常见的数论积性函数:
-
莫比乌斯函数 。
-
欧拉函数 : 中与 互质的数的个数。
-
约数个数 : 的约数个数。
-
约数和 :。
-
元函数 。即 。
-
单位函数 。即为 。
-
恒等函数 。该函数恒为 。有时也写作 。
其中后三个函数为完全积性函数。
2. 狄利克雷卷积
定义两个数论函数的狄利克雷卷积为 ,读作 卷 。
其中后面括号里的 代表卷积范围。
卷积有下面这些良好的性质:
-
交换律:。
-
结合律:。
上面的性质可以根据卷积定义证明。这里略过。
3. 常见数论函数的狄利克雷卷积
证明:。
上文提到过,。
利用欧拉函数性质证明。
根据性质 , 推出
若 ,则利用 和积性函数的性质即可推出。
证明:由于 ,根据卷积的性质,可以在等式两边同时卷上 ,得到 。根据 得 。证毕。
证明:。
3. 莫比乌斯反演
莫比乌斯反演的形式如下:
如果存在 ,那么有 。
该结论可以利用狄利克雷卷积来证明。
证明:
写成卷积的形式就是 。那么对两边同时卷上 ,可得 。由于 ,所以原式可化为 ,即 。证毕。
某些例题:
下面求和的上界省略了下取整符号。
求 。
大力莫反。
前面的 可以线筛,后面的可以整除分块套上 前缀和。
void get_mu(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i ++ ) {
if (!is_prime[i]) mu[i] = -1, p[ ++ cnt] = i;
for (int j = 1; j <= cnt and i * p[j] <= n; j ++ ) {
is_prime[i * p[j]] = true;
if (i % p[j] == 0) break;
mu[i * p[j]] = -mu[i];
}
}
for (int i = 1; i <= n; i ++ )
s[i] = s[i - 1] + mu[i];
}
int main() {
get_mu(N - 5);
scanf("%d", &T);
while (T -- ) {
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
int ans = 0;
for (int l = 1, r; l <= min(n, m); l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += (n / (l * k)) * (m / (l * k)) * (s[r] - s[l - 1]);
}
printf("%d\n", ans);
}
return 0;
}
求 。
上一道题的强化版。将上一道题写成函数 ,表示求 求 的值。然后利用类似二维前缀和的方式,计算出答案即为
首先考虑计算 。
首先枚举 ,得到
继续化简:
可以前缀和,然后整除分块就好了。复杂度 ,瓶颈在前缀和。
原题要求的就是刚才求的减去 之后在除以 。这里根据最大公约数的交换律或者对称性易得。
另外,这个题也可以不必卷到 。只要卷到 就可以套两层整除分块了。这样的复杂度是 ,也很优秀。
一天后返回来在看一眼原来推得式子,会发现确实推复杂了。正难则反,直接用 来推。
可以发现结果是一样的。
- 两层整除分块
void get_mu(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i ++ ) {
if (!is_prime[i]) mu[i] = -1, p[ ++ cnt] = i;
for (int j = 1; j <= cnt and p[j] * i <= n; j ++ ) {
is_prime[i * p[j]] = true;
if (i % p[j] == 0) break;
mu[i * p[j]] = -mu[i];
}
}
for (int i = 1; i <= n; i ++ )
s[i] = s[i - 1] + mu[i];
}
int prob(int l, int r) {
return (l + r) * (r - l + 1) >> 1;
}
int calc(int n) {
int ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ans += (s[r] - s[l - 1]) * (n / l) * (n / l);
}
return ans;
}
signed main() {
scanf("%lld", &n);
get_mu(n);
int ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ans += prob(l, r) * calc(n / l);
}
ans -= n * (n + 1) >> 1;
printf("%lld\n", ans >> 1);
return 0;
}
- 卷到
void get_mu(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i ++ ) {
if (!is_prime[i]) phi[i] = i - 1, p[ ++ cnt] = i;
for (int j = 1; j <= cnt and i * p[j] <= n; j ++ ) {
is_prime[i * p[j]] = true;
if (i % p[j] == 0) {
phi[i * p[j]] = phi[i] * p[j];
break;
}
phi[i * p[j]] = phi[i] * phi[p[j]];
}
}
for (int i = 1; i <= n; i ++ )
s[i] = s[i - 1] + phi[i];
}
signed main() {
scanf("%lld", &n);
get_mu(n);
int ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ans += (n / l) * (n / l) * (s[r] - s[l - 1]);
}
ans -= n * (n + 1) >> 1;
printf("%lld\n", ans >> 1);
return 0;
}
第二种做法比第一种做法快约 。
设质数集为 ,求:
又到了愉快的推式子时间:这里设 ,
然后发现两个下取整可以直接整除分块,瓶颈就在计算 上。这个玩意可以筛出质数后预处理。
预处理复杂度 ,计算复杂度 。总复杂度 。
看了一下题解,预处理也可以线性筛。不过不想看了,反正能过就行。
void init() {
mu[1] = 1;
for (int i = 2; i <= N - 5; i ++ ) {
if (!is_prime[i]) p[ ++ cnt] = i, mu[i] = -1;
for (int j = 1; j <= cnt and i * p[j] <= N - 5; j ++ ) {
is_prime[i * p[j]] = true;
if (i % p[j] == 0) break;
mu[i * p[j]] = - mu[i];
}
}
for (int i = 1; i <= cnt; i ++ )
for (int j = 1; p[i] * j <= N - 5; j ++ )
f[p[i] * j] += mu[j];
for (int i = 1; i <= N - 5; i ++ )
s[i] = s[i - 1] + f[i];
}
void substack() {
scanf("%lld%lld", &n, &m);
if (n > m) swap(n, m);
int ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += (n / l) * (m / l) * (s[r] - s[l - 1]);
}
printf("%lld\n", ans);
}
设 为 的约数个数,给定 ,求
有结论:
于是可以愉快地推柿子了。
不妨设 ,可以发现这个玩意就等于 。而这个东西可以筛 的时候预处理出来。
于是柿子变成 。整出分块即可。总复杂度 。预处理可以用线性筛,但是懒得写了,直接莽了个 暴力上去。
void init(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i ++ ) {
if (!is_prime[i]) p[ ++ cnt] = i, mu[i] = -1;
for (int j = 1; j <= cnt and i * p[j] <= n; j ++ ) {
is_prime[i * p[j]] = true;
if (i % p[j] == 0) break;
mu[i * p[j]] = -mu[i];
}
}
for (int i = 1; i <= n; i ++ )
mu_s[i] = mu_s[i - 1] + mu[i];
for (int i = 1; i <= n; i ++ )
for (int j = i; j <= n; j += i)
s[j] ++ ;
for (int i = 1; i <= n; i ++ )
s[i] += s[i - 1];
}
void substack() {
scanf("%lld%lld", &n, &m);
int ans = 0;
for (int l = 1, r; l <= min(n, m); l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += s[n / l] * s[m / l] * (mu_s[r] - mu_s[l - 1]);
}
printf("%lld\n", ans);
}
两年半两天半,终于把莫反学完了。再次小记,以免自己以后再忘掉。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示