莫比乌斯反演和数论函数
感觉一直没有认真学过数论,这次好好记一下。
前置知识:整除分块,筛法。
一些符号说明:
有些时候会用
若非特殊说明,除法均为下取整。
Part 1 基础数论函数 & 狄利克雷卷积
基础数论函数
数论函数指定义域为整数的函数,也可视作一个数列。
一些常用的数论函数:
- 单位函数:
。 - 恒等函数:
。特别地,当 的时候通常简记作 。 - 常数函数:
。 - 除数函数:
。特别地,当 的时候通常简记作 , 通常简记作 或 。 - 欧拉函数:
,即表示 中与 互质的数的个数。 - 莫比乌斯函数:
,其中 表示 本质不同的质因子个数。
完全积性函数:若函数
积性函数:若函数
积性函数的一个性质:
狄利克雷(Dirichlet)卷积
定义两个数论函数
性质:交换律(
单位元:单位函数
逆元:对于任意满足
重要结论:
两个积性函数的 Dirichlet 卷积也是积性函数
证明:记
。 则根据积性函数的定义,有:
。
, 同理。 则有
因为
,所以有 ,则可继续化为 所以
为积性函数,原命题得证。
积性函数的逆元也是积性函数。
证明:由于表示出
的式子是一个由大规模到小规模的样子,考虑使用归纳法证明。 设
,不妨令 。 当
,则 ,此时结论显然成立。 若
,假设当前所有 的结论均成立,则有: 所以
为积性函数。
一些常用的 Dirichlet 卷积的例子:
。
Part 2 莫比乌斯反演
引入
过程:
首先
显然为 。 由于
是积性函数,所以 一定也是积性函数,不妨先考虑 ( )的值。
,由于 ,所以 ,可以得到 。 ,有 ,所以 ,对 从小到大考虑并带入 的值后,易得 。 然后由于
是积性函数,所以所有含有平方或更高次方的因子的时候,均有 。 然后剩下的数肯定就是由若干个本质不同的质数相乘得到的,则
,所以 , 就表示 的本质不同的质数个数。 容易发现
的式子就是上面的形式。
由
线筛
发现
Code:
bool vis[500010];
int cnt, p[500010], mu[500010];
void init(int n) {
mu[1] = 1;
for(int i = 2; i <= n; ++i) {
if(!vis[i])
mu[i] = -1, p[++cnt] = i;
for(int j = 1; j <= cnt && i * p[j] <= n; ++j) {
mu[i * p[j]] = i % p[j] == 0 ? 0 : -mu[i];
vis[i * p[j]] = 1;
if(i % p[j] == 0)
break;
}
}
}
然后就是介绍怎么反演了。
反演公式
设
第一种:若
第二种:若
莫反的一个重要结论:
拓展:
证明:
当
时, 有
然后由于
和 是积性函数,所以这里就只证明 的取值满足就行了。
由
Part 3 一些例题
1 一道经典的题
Problem
给定
要求在
Sol
因为
然后我们交换求和顺序,变为
Code
没有。
2 P2522 [HAOI 2011] Problem b](https://www.luogu.com.cn/problem/P2522)
Problem
有
Sol
显然可以先将平面上的矩形查询变通过容斥变为前缀矩形的贡献,不妨令
考虑如何求解
然后由例题一可知
相同的题:P3455 [POI2007] ZAP-Queries。
Code
#include<bits/stdc++.h>
#define ll long long
#define sz(a) ((int) (a).size())
#define vi vector < int >
#define pb emplace_back
using namespace std;
bool vis[50010];
int cnt, p[50010], mu[50010], ms[50010];
void euler(int n) {
mu[1] = 1;
for(int i = 2; i <= n; ++i) {
if(!vis[i])
mu[i] = -1, p[++cnt] = i;
for(int j = 1; j <= cnt && i * p[j] <= n; ++j) {
mu[i * p[j]] = i % p[j] == 0 ? 0 : -mu[i];
vis[i * p[j]] = 1;
if(p[j] % i == 0)
break;
}
}
for(int i = 1; i <= n; ++i)
ms[i] = ms[i - 1] + mu[i];
}
ll work(int n, int m) {
if(n > m)
swap(n, m);
ll res = 0;
for(int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
res += (n / l) * 1ll * (m / l) * (ms[r] - ms[l - 1]);
}
return res;
}
void solve() {
int a, b, c, d, k;
cin >> a >> b >> c >> d >> k;
cout << work(b / k, d / k) - work((a - 1) / k, d / k) - work(b / k, (c - 1) / k) + work((a - 1) / k, (c - 1) / k) << "\n";
}
int main() {
ios :: sync_with_stdio(false);
cin.tie(0); cout.tie(0);
euler(50000);
int T;
cin >> T;
while(T--)
solve();
return 0;
}
3 P2257 YY的GCD
Problem
给定
Sol
不妨令
显然枚举
Code
#include<bits/stdc++.h>
#define ll long long
#define sz(a) ((int) (a).size())
#define vi vector < int >
#define pb emplace_back
using namespace std;
bool vis[10000010];
int cnt, p[10000010], mu[10000010], ms[10000010];
ll g[10000010];
void euler(int n) {
mu[1] = 1;
for(int i = 2; i <= n; ++i) {
if(!vis[i])
mu[i] = -1, p[++cnt] = i;
for(int j = 1; j <= cnt && i * p[j] <= n; ++j) {
mu[i * p[j]] = i % p[j] == 0 ? 0 : -mu[i];
vis[i * p[j]] = 1;
if(p[j] % i == 0)
break;
}
}
for(int i = 1; i <= cnt; ++i)
for(int j = 1; p[i] * j <= n; ++j)
g[p[i] * j] += mu[j];
for(int i = 1; i <= n; ++i)
ms[i] = ms[i - 1] + mu[i], g[i] += g[i - 1];
}
ll work(int n, int m) {
if(n > m)
swap(n, m);
ll res = 0;
for(int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
res += (n / l) * 1ll * (m / l) * (g[r] - g[l - 1]);
}
return res;
}
void solve() {
int n, m;
cin >> n >> m;
cout << work(n, m) << "\n";
}
int main() {
ios :: sync_with_stdio(false);
cin.tie(0); cout.tie(0);
euler(10000000);
int T;
cin >> T;
while(T--)
solve();
return 0;
}
双倍经验:SP4491。
4 P3327 [SDOI2015] 约数个数和
Problem
设
Sol
首先这个
这里的
然后整除分块即可。
时间复杂度:
Code
#include<bits/stdc++.h>
#define ll long long
#define sz(a) ((int) (a).size())
#define vi vector < int >
#define pb emplace_back
using namespace std;
bool vis[50010];
int cnt, p[50010], mu[50010], ms[50010];
void euler(int n) {
mu[1] = 1;
for(int i = 2; i <= n; ++i) {
if(!vis[i])
p[++cnt] = i, mu[i] = -1;
for(int j = 1; j <= cnt && i * p[j] <= n; ++j) {
vis[i * p[j]] = 1;
mu[i * p[j]] = i % p[j] == 0 ? 0 : -mu[i];
if(i % p[j] == 0)
break;
}
}
for(int i = 1; i <= n; ++i)
ms[i] = ms[i - 1] + mu[i];
}
int s[50010];
ll work(int n) {
ll res = 0;
for(int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
res += (n / l) * (r - l + 1ll);
}
return res;
}
void init(int n) {
euler(n);
for(int i = 1; i <= n; ++i)
s[i] = work(i);
}
ll g(int n, int m) {
return s[n] * 1ll * s[m];
}
int n, m;
void solve() {
cin >> n >> m;
if(n > m)
swap(n, m);
ll ans = 0;
for(int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += (ms[r] - ms[l - 1]) * g(n / l, m / l);
}
cout << ans << "\n";
}
int main() {
ios :: sync_with_stdio(false);
cin.tie(0); cout.tie(0);
init(50000);
int T;
cin >> T;
while(T--)
solve();
return 0;
}
/*
2
32 43
53 51
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通