学习笔记:莫比乌斯函数
前置知识:迪利克雷卷积,整除分块。
莫比乌斯函数
积性函数,即
基本性质
性质 1
莫比乌斯函数是数论函数
证明在性质 2。
性质 2
证明
设
, , 都是质数,就是唯一分解定理,
则,因为只有次数为 的质数才有贡献,不然就有平方因子,贡献为 ,
则,
然后根据二项式定理,,
因此当且仅当即 时, ,否则为 。
同时也证明了性质 1。
该性质有一些比较常见的运用,例如,
性质 3 ( 莫比乌斯变换 / 逆变换(反演) )
一般有两个形式。
形式 1
若
我们称
证明
。
或者用迪利克雷卷积证明,
。
形式 2
若
证明
。
练习
P3455 [POI2007] ZAP-Queries
Solution
设
根据整除分块以及整除点的性质可知,使得
这段也可以用莫比乌斯反演来推。
有一个常见的技巧,互质不方便直接判断,但是判断和 的 是不是某个数 的倍数是简单的,只需要 同时是 和 的约数。
设,
令,
容易得到,
根据莫比乌斯反演形式 2,。
枚举,于是 。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 5e4 + 5;
int n, m, d;
int mob[N], prime[N], vis[N], cnt;
ll sum[N];
void Solve() {
cin >> n >> m >> d;
n /= d, m /= d;
ll ans = 0, l1 = 1, r1 = 1, l2 = 1, r2 = 1;
while (l1 <= n && l2 <= m) {
r1 = n / (n / l1);
r2 = m / (m / l2);
ans += (sum[min(r1, r2)] - sum[max(l1, l2) - 1]) * (n / r1) * (m / r2);
// cout << l1 << ' ' << r1 << ' ' << l2 << ' ' << r2 << '\n';
if (r1 <= r2) l1 = r1 + 1;
if (r1 >= r2) l2 = r2 + 1;
}
cout << ans << '\n';
}
int main() {
cnt = 0;
mob[1] = sum[1] = 1;
for (int i = 2; i <= 5e4; i++) {
if (!vis[i]) {
prime[++cnt] = i;
vis[i] = i;
mob[i] = -1;
}
sum[i] = sum[i - 1] + mob[i];
for (int j = 1; j <= cnt && prime[j] <= 5e4 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[i * prime[j]] = 0;
break;
} else {
mob[i * prime[j]] = -mob[i];
}
}
}
int T;
cin >> T;
while (T--) Solve();
return 0;
}
P2257 YY的GCD
Solution
设
设
原式
考虑枚举
原式
前一半
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 1e7 + 5;
int n, m;
int prime[N], cnt, vis[N], mob[N];
ll res[N], sum[N];
void Solve() {
cin >> n >> m;
int l1 = 1, r1, l2 = 1, r2;
ll ans = 0;
while (l1 <= n && l2 <= m) {
r1 = n / (n / l1);
r2 = m / (m / l2);
ans += (1ll * n / r1) * (1ll * m / r2) * (sum[min(r1, r2)] - sum[max(l1, l2) - 1]);
if (r1 <= r2) l1 = r1 + 1;
if (r2 <= r1) l2 = r2 + 1;
}
cout << ans << '\n';
}
int main() {
mob[1] = 1;
for (int i = 2; i <= 1e7; i++) {
if (!vis[i]) {
prime[++cnt] = i;
vis[i] = i;
mob[i] = -1;
}
for (int j = 1; j <= cnt && prime[j] <= 1e7 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[prime[j] * i] = 0;
break;
} else {
mob[prime[j] * i] = -mob[i];
}
}
}
for (int i = 1; i <= cnt; i++) {
for (int j = prime[i]; j <= 1e7; j += prime[i]) {
res[j] += (ll)mob[j / prime[i]];
}
}
for (int i = 1; i <= 1e7; i++) {
sum[i] = sum[i - 1] + res[i];
}
int T;
cin >> T;
while (T--) Solve();
return 0;
}
P3312 [SDOI2014] 数表
Solution
设
其中
原式
设
原式
如果没有限制
那么我们考虑离线处理,按照询问的
因此需要实时更改前缀和,于是树状数组维护即可。时间复杂度
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 1e5 + 5;
const ll mod = 1ll << 31;
int q;
ll ans[N];
ll prime[N], vis[N], mob[N], c[N], p[N]; int cnt;
// c[i] 是 i 的最小质因子的次数。
// p[i] 是 i 的最小质因子的 c[i] 次方。
struct Info {
int n, m, idx;
ll a;
bool operator < (const Info &_) const { return a < _.a; }
} A[N];
struct Sigma { // σ1函数
ll val, x;
bool operator < (const Sigma &_) const { return val < _.val; }
} sig[N];
struct BitTree {
int n;
vector<ll> a;
void init(int _n) {
n = _n;
a.assign(n + 5, 0);
}
void add(int x, ll v) {
for (; x <= n; x += (x & -x)) a[x] += v;
}
ll sum(int x) {
ll ans = 0;
for (; x; x -= (x & -x)) ans += a[x];
return ans;
}
ll sum(int l, int r) {
return sum(r) - sum(l - 1);
}
} t;
ll work(int n, int m) {
int l = 1, r;
ll ans = 0;
if (n > m) swap(n, m);
for (; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += t.sum(l, r) * (1ll * n / l) * (1ll * m / l);
}
return ans;
}
int main() {
mob[1] = 1;
sig[1] = {1, 1};
for (int i = 2; i <= 1e5; i++) {
if (!vis[i]) {
vis[i] = i;
prime[++cnt] = i;
mob[i] = -1;
sig[i] = {i + 1, i};
c[i] = 1;
p[i] = i;
}
for (int j = 1; j <= cnt && prime[j] <= 1e5 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[prime[j] * i] = 0;
c[prime[j] * i] = c[i] + 1;
p[prime[j] * i] = p[i] * prime[j];
if (p[i] == i) {
sig[prime[j] * i] = {sig[i].val + p[i] * prime[j], prime[j] * i};
} else {
sig[prime[j] * i] = {sig[i / p[i]].val * sig[p[i] * prime[j]].val, prime[j] * i};
}
break;
} else {
c[prime[j] * i] = 1;
p[prime[j] * i] = prime[j];
mob[prime[j] * i] = -mob[i];
sig[prime[j] * i] = {sig[i].val * sig[prime[j]].val, prime[j] * i};
}
}
}
sort(sig + 1, sig + 1 + (int)1e5);
t.init(1e5);
cin >> q;
for (int i = 1; i <= q; i++) {
cin >> A[i].n >> A[i].m >> A[i].a;
A[i].idx = i;
}
sort(A + 1, A + 1 + q);
for (int i = 1, j = 1; i <= q; i++) {
while (j <= 1e5 && sig[j].val <= A[i].a) {
for (int k = sig[j].x; k <= 1e5; k += sig[j].x) {
t.add(k, sig[j].val * mob[k / sig[j].x]);
}
j++;
}
ans[A[i].idx] = work(A[i].n, A[i].m);
}
for (int i = 1; i <= q; i++) cout << ans[i] % mod << '\n';
return 0;
}
P3327 [SDOI2015] 约数个数和
Solution
重要性质:
证明:
设, ,
那么对于的每个质因子 ,我们要保证 中 的次数为 , 中 的次数为 ,且 ,
通俗来说,相当于可以从中拿了 个 出来, 中拿了 个 出来,满足 ,
那么我们钦定中如果 ,也就是直接能拿够就直接拿,否则在 中拿剩下的 个,
因此不会同时从和 拿出相同的质因子,所以 ,代表从 中拿出了 ,从 中拿出了剩余不够的 ,凑成了一个 的因子 。
设
令
易知,
将限制条件同时除去
由莫比乌斯反演,得,
设
, 即可预处理,
易知,,于是便能快速算出 函数,
由题意,最终答案即
于是整除分块即可。
时间复杂度
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 5e4 + 5;
int n, m;
ll prime[N], vis[N], mob[N]; int cnt;
ll sum[N], h[N];
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 += (sum[r] - sum[l - 1]) * h[n / l] * h[m / l];
}
cout << ans << '\n';
}
int main() {
sum[1] = mob[1] = 1;
for (int i = 2; i <= 5e4; i++) {
if (!vis[i]) {
vis[i] = i;
prime[++cnt] = i;
mob[i] = -1;
}
for (int j = 1; j <= cnt && prime[j] <= 5e4 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[prime[j] * i] = 0;
break;
} else {
mob[prime[j] * i] = -mob[i];
}
}
sum[i] = sum[i - 1] + mob[i];
}
for (int i = 1; i <= 5e4; i++) {
for (int l = 1, r; l <= i; l = r + 1) {
r = i / (i / l);
h[i] += (1ll * i / l) * (r - l + 1);
}
}
int T;
cin >> T;
while (T--) Solve();
return 0;
}
P1829 [国家集训队] Crash的数字表格 / JZPTAB
Solution
设
则 原式
设
然后预处理
发现最外层仍满足整除分块的性质,
于是嵌套两个整除分块即可,时间复杂度
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 1e7 + 5;
const ll mod = 20101009;
int n, m;
ll prime[N], vis[N], mob[N]; int cnt;
ll sum[N], a[N];
ll calc(int n, int m) {
ll ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans = (ans + ((sum[r] - sum[l - 1] + mod) % mod) % mod * a[n / l] % mod * a[m / l] % mod) % mod;
}
return ans;
}
int main() {
sum[1] = mob[1] = 1;
for (int i = 2; i <= 1e7; i++) {
if (!vis[i]) {
vis[i] = i;
prime[++cnt] = i;
mob[i] = -1;
}
for (int j = 1; j <= cnt && prime[j] <= 1e7 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[prime[j] * i] = 0;
break;
} else {
mob[prime[j] * i] = -mob[i];
}
}
sum[i] = (sum[i - 1] + mob[i] * i % mod * i % mod) % mod;
}
for (int i = 1; i <= 1e7; i++) {
a[i] = (a[i - 1] + i) % mod;
}
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 = (ans + (a[r] - a[l - 1] + mod) % mod * calc(n / l, m / l) % mod) % mod;
}
cout << ans << '\n';
return 0;
}
本文作者:chenwenmo
本文链接:https://www.cnblogs.com/chenwenmo/p/18593492
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探