浅谈莫比乌斯反演
狄利克雷卷积
定义两个数论函数的狄利克雷卷积为:
其中单位元
可以证明狄利克雷卷积满足交换律及结合律
莫比乌斯函数
对于,定义莫比乌斯函数:
对于莫比乌斯函数,存在一个很重要的性质:
证明:
我们考虑枚举的所有因子,显然某个质因子的指数时该函数值为,对结果不产生贡献,故当我们对于定义时,有:
考虑的所有质因子,枚举因数等价于从中选择任意个数的质因子进行组合,所以显然有:
反向利用二项式定理:
显然原式当且仅当时等于,否则等于
当时有,故我们证明了:
同时该结论也可以写成狄利克雷卷积的形式:
注意到莫比乌斯函数是积性函数,所以很好求,线性筛就可以了
inline void init(int n) {
int i, j;
mu[1] = 1;
for (i = 1; i <= n; ++i) isprime[i] = 1;
for (i = 2; i <= n; ++i) {
if (isprime[i]) prime.push_back(i), mu[i] = -1;
for (j = 0; j < prime.size() && i * prime[j] <= n; ++j) {
isprime[i * prime[j]] = 0;
if (!(i % prime[j])) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = mu[i] * mu[prime[j]];
}
}
}
莫比乌斯反演
对于数论函数,如果有:
则可以推出:
证明:
将等式右边写成狄利克雷卷积的形式:
同样将条件写成狄利克雷卷积的形式:
所以:
即:
于是我们证明了莫比乌斯反演定理
易证上式也有个等价形式:
同时我们还有第二种反演形式:
若
则同样可以推出
证明:
设:
整除分块
求
显然暴力计算复杂度为,但我们注意到很多值其实是一样的,所以我们可以把值相同的统一计算,分块处理:
for (int l = 1, r; l <= n; l = r + 1) {
r = (n / (n / l));
ans += (r - l + 1) * (n / l);
}
复杂度
例题
[POI2007]ZAP-Queries
组询问,给出,求
Solution 1
我们设:
可以发现就是我们要求的答案
显然根据莫比乌斯反演定理可以得到:
注意到:
所以:
设,枚举:
前面的可以前缀和预处理,后面用整除分块即可
总复杂度
#include <bits/stdc++.h>
using namespace std;
#define MAXN 50005
int mu[MAXN], sum[MAXN];
bool isprime[MAXN];
vector<int> prime;
inline void init(int n) {
int i, j;
mu[1] = 1;
for (i = 1; i <= n; ++i) isprime[i] = 1;
for (i = 2; i <= n; ++i) {
if (isprime[i]) prime.push_back(i), mu[i] = -1;
for (j = 0; j < prime.size() && i * prime[j] <= n; ++j) {
isprime[i * prime[j]] = 0;
if (!(i % prime[j])) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = mu[i] * mu[prime[j]];
}
}
for (i = 1; i <= n; ++i) sum[i] = sum[i - 1] + mu[i];
}
int T, a, b, d;
signed main() {
init(MAXN);
scanf("%d", &T);
while (T--) {
scanf("%d%d%d", &a, &b, &d);
int n = min(a / d, b / d);
int l, r;
int ans = 0;
for (l = 1; l <= n; l = r + 1) {
r = min((a / d) / ((a / d) / l), (b / d) / ((b / d) / l));
ans += ((a / d) / l) * ((b / d) / l) * (sum[r] - sum[l - 1]);
}
printf("%d\n", ans);
}
return 0;
}
Solution 2
可以发现上述利用莫比乌斯反演定理构造函数进行求解的过程不太好想,其实我们还可以利用莫比乌斯函数的性质顺向思考求解
我们要求:
(这里用替换避免变量名重复)
约去:
莫比乌斯函数有个性质:
所以:
带回原式:
枚举:
可以发现当且仅当均为的倍数时,同时在中的倍数有个,所以原式等价于:
结果与一致
[HAOI2011]Problem b
组询问,给出,求
做法和上一题几乎完全一致,容斥一下即可
令:
显然有:
#include <bits/stdc++.h>
using namespace std;
#define MAXN 50005
int mu[MAXN], sum[MAXN];
bool isprime[MAXN];
vector<int> prime;
inline void init(int n) {
int i, j;
mu[1] = 1;
for (i = 1; i <= n; ++i) isprime[i] = 1;
for (i = 2; i <= n; ++i) {
if (isprime[i]) prime.push_back(i), mu[i] = -1;
for (j = 0; j < prime.size() && i * prime[j] <= n; ++j) {
isprime[i * prime[j]] = 0;
if (!(i % prime[j])) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = mu[i] * mu[prime[j]];
}
}
for (i = 1; i <= n; ++i) sum[i] = sum[i - 1] + mu[i];
}
inline int solve(int a, int b, int d) {
int n = min(a / d, b / d);
int l, r;
int ans = 0;
for (l = 1; l <= n; l = r + 1) {
r = min((a / d) / ((a / d) / l), (b / d) / ((b / d) / l));
ans += ((a / d) / l) * ((b / d) / l) * (sum[r] - sum[l - 1]);
}
return ans;
}
int T, a, b, c, d, k;
signed main() {
init(MAXN);
scanf("%d", &T);
while (T--) {
scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
printf("%d\n", solve(b, d, k) - solve(b, c - 1, k) -
solve(a - 1, d, k) + solve(a - 1, c - 1, k));
}
return 0;
}
P2257 YY的GCD
给定 ,求 且 为质数的 有多少对。
简化题意就是要求:
Solution 1
枚举质数:
提出:
替换:
枚举:
显然由于之前的经验上式等于:
令,枚举:
令,则原式等于:
注意到如果我们求出了的前缀和,即可像之前一样通过整除分块快速求解
考虑的取值:
令,其中是的最小质因子
若,显然
否则,考虑
若
若,则当切仅当枚举的时,所以有
若,则无论何时,所以有
若,则,即中包含,同时和相比多出的一项就是当时,,所以
于是我们得到了的方程:
可以通过线性筛求出的前缀和
#include <bits/stdc++.h>
using namespace std;
#define MAXN 10000005
#define int long long
int mu[MAXN], sum[MAXN], f[MAXN];
bool isprime[MAXN];
vector<int> prime;
inline void init(int n) {
int i, j;
mu[1] = 1;
for (i = 1; i <= n; ++i) isprime[i] = 1;
for (i = 2; i <= n; ++i) {
if (isprime[i]) prime.push_back(i), mu[i] = -1, f[i] = 1;
for (j = 0; j < prime.size() && i * prime[j] <= n; ++j) {
isprime[i * prime[j]] = 0;
if (!(i % prime[j])) {
f[i * prime[j]] = mu[i];
mu[i * prime[j]] = 0;
break;
} else {
f[i * prime[j]] = -f[i] + mu[i];
mu[i * prime[j]] = mu[i] * mu[prime[j]];
}
}
f[i] += f[i - 1];
}
}
int T, a, b, d;
signed main() {
init(MAXN);
scanf("%lld", &T);
while (T--) {
scanf("%lld%lld", &a, &b);
int n = min(a, b);
int l, r;
int ans = 0;
for (l = 1; l <= n; l = r + 1) {
r = min(a / (a / l), b / (b / l));
ans += (a / l) * (b / l) * (f[r] - f[l - 1]);
}
printf("%lld\n", ans);
}
return 0;
}
在你谷因为用了vector所以上述代码吸氧才能过
Solution 2
我们以前做过这道题:
当时我们利用了这个性质
现在我们要求:
那我们也可以自己构造一个函数满足
我们先给赋初值:
考虑如何让:
for (i = 2; i <= MAXN; ++i) {
for (j = i << 1; j <= MAXN; j += i) {
f[j] -= f[i];
}
}
不难发现如此处理即可得到合法的
[SDOI2015]约数个数和
设为的约数个数,组询问,求
首先有一个结论:
证明:
设(不存在该质因子则指数为),显然有:
对于某个质因子,考虑等式右边如果要满足,显然不能同时拥有因子。令中含有,中含有:
如果中不含因子,则中可选质因子次数为
如果中不含因子,则中可选质因子次数为
二者相加再减去都不含的一种情况,结果数为
考虑乘法原理,对于每个结果数都是,所以:
证毕
有了上述结论,我们就可以很轻松的化简原式:
枚举:
设:
莫比乌斯反演:
注意到:
约去:
显然答案为:
可以整除分块预处理出,从而快速求出
#include <bits/stdc++.h>
using namespace std;
#define MAXN 50005
#define int long long
int mu[MAXN], sum[MAXN], s[MAXN];
bool isprime[MAXN];
vector<int> prime;
inline void init(int n) {
int i, j;
mu[1] = 1;
for (i = 1; i <= n; ++i) isprime[i] = 1;
for (i = 2; i <= n; ++i) {
if (isprime[i]) prime.push_back(i), mu[i] = -1;
for (j = 0; j < prime.size() && i * prime[j] <= n; ++j) {
isprime[i * prime[j]] = 0;
if (!(i % prime[j])) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = mu[i] * mu[prime[j]];
}
}
for (i = 1; i <= n; ++i) sum[i] = sum[i - 1] + mu[i];
for (i = 1; i <= n; ++i) {
int ans = 0;
int l, r;
for (l = 1; l <= i; l = r + 1) {
r = i / (i / l);
ans += (i / l) * (r - l + 1);
}
s[i] = ans;
}
}
int T, a, b;
signed main() {
init(MAXN);
scanf("%lld", &T);
while (T--) {
scanf("%lld%lld", &a, &b);
int n = min(a, b);
int l, r;
int ans = 0;
for (l = 1; l <= n; l = r + 1) {
r = min(a / (a / l), b / (b / l));
ans += s[a / l] * s[b / l] * (sum[r] - sum[l - 1]);
}
printf("%lld\n", ans);
}
return 0;
}
[国家集训队]Crash的数字表格
给出,求
答案对取模
令,则显然有
所以:
最后两项显然是等差数列求和,可以用整除分块优化
#include <bits/stdc++.h>
using namespace std;
#define MAXN 10000005
#define mod 20101009
#define int long long
int mu[MAXN], sum[MAXN], s[MAXN];
bool isprime[MAXN];
vector<int> prime;
inline void init(int n) {
int i, j;
mu[1] = 1;
for (i = 1; i <= n; ++i) isprime[i] = 1;
for (i = 2; i <= n; ++i) {
if (isprime[i]) prime.push_back(i), mu[i] = -1;
for (j = 0; j < prime.size() && i * prime[j] <= n; ++j) {
isprime[i * prime[j]] = 0;
if (!(i % prime[j])) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = mu[i] * mu[prime[j]];
}
}
for (i = 1; i <= n; ++i)
sum[i] = (sum[i - 1] + mu[i] * 1ll * i % mod * 1ll * i % mod) % mod;
}
int a, b;
signed main() {
scanf("%lld%lld", &a, &b);
init(min(a, b));
int n = min(a, b);
int l, r, i, j;
int inv = (mod + 1ll) / 2ll;
int ans = 0, tsum = 0;
for (i = 1; i <= n; ++i) {
int x = a / i, y = b / i;
int nn = min(x, y);
tsum = 0;
for (l = 1; l <= nn; l = r + 1) {
r = min(x / (x / l), y / (y / l));
tsum =
(tsum +
(sum[r] - sum[l - 1]) % mod * 1ll *
(((1 + (x / l)) % mod * 1ll * (x / l) % mod) * inv % mod) %
mod * 1ll *
(((1 + (y / l)) % mod * 1ll * (y / l) % mod) * inv % mod) %
mod) %
mod;
}
ans = (ans + tsum * i % mod) % mod;
}
printf("%lld\n", (ans % mod + mod) % mod);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步