莫比乌斯反演
莫比乌斯函数的定义
要学会莫比乌斯反演,首先我们得明白,莫比乌斯函数的定义是什么
莫比乌斯函数(Möbius 函数)定义为:

首先我们知道,任何一个数都可以用若干个素数相乘表示,对于莫比乌斯函数,如果存在一个素数的次幂大于1,那么莫比乌斯的函数值为0,否则的话就定义k为互异素因子的个数,莫比乌斯函数的值就是-1的k次幂
莫比乌斯函数的求法
对于单个数,我们可以对他分解质因数,然后计算他的莫比乌斯函数
int mu(int n) {
int res = 1;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
n /= i;
// 检查是否是2次幂
if (n % i == 0) return 0;
res = -res;
}
}
// The remaining factor must be prime.
if (n > 1) res = -res;
return res;
}
对于多个数字,我们采取一线性筛来用O(N)的复杂度完成
std::vector<int> get_mu(int n) {
// mu:存储莫比乌斯函数值,下标 0~n(0 unused,1~n 对应实际值)
// primes:存储筛选过程中发现的素数(用于后续筛非素数)
std::vector<int> mu(n + 1), primes;
// not_prime:标记是否为非素数(true=非素数,false=素数),初始默认全为素数(false)
std::vector<bool> not_prime(n + 1);
// 预分配 primes 内存(避免动态扩容开销),预估素数个数约为 n / ln(n),此处简化用 n
primes.reserve(n);
// 初始化:根据莫比乌斯函数定义,μ(1) = 1
mu[1] = 1;
// 外层循环:遍历 2~n 的所有数,处理每个数 x
for (int x = 2; x <= n; ++x) {
// 若 x 未被标记为非素数,说明 x 是素数
if (!not_prime[x]) {
primes.push_back(x); // 将素数 x 加入素数列表
mu[x] = -1; // 素数是「无平方因子且仅1个素因子」,故 μ(x) = (-1)^1 = -1
}
// 内层循环:用已发现的素数 p 筛 x*p(线性筛核心:每个数仅被最小素因子筛一次)
for (int p : primes) {
// 边界判断:x*p 超过 n 时,无需继续(避免数组越界)
if (x * p > n) break;
// 标记 x*p 为非素数(x*p 是素数 p 的倍数,必然非素数)
not_prime[x * p] = true;
// 关键判断:x 是否能被当前素数 p 整除
if (x % p == 0) {
// 情况1:x 能被 p 整除 → x 中已包含素因子 p
// 则 x*p 中包含 p²(x 有 p,再乘 p),即含平方因子 → μ(x*p) = 0
mu[x * p] = 0;
break; // 线性筛核心:p 是 x 的最小素因子,后续素数更大,无需继续(避免重复筛)
} else {
// 情况2:x 不能被 p 整除 → p 是 x*p 的「新素因子」
// x 的素因子个数为 k,则 x*p 的素因子个数为 k+1 → μ(x*p) = (-1)^(k+1) = -(-1)^k = -μ(x)
mu[x * p] = -mu[x];
}
}
}
// 返回 1~n 的莫比乌斯函数值数组
return mu;
}
莫比乌斯反演
在我们了解了莫比乌斯函数后,现在就该用他的性质,莫比乌斯反演

然后这个有什么用呢?
我们接下来来写一个题
P2522 [HAOI2011] Problem b
题目描述
对于给出的 \(n\) 个询问,每次求有多少个数对 \((x,y)\),满足 \(a \le x \le b\),\(c \le y \le d\),且 \(\gcd(x,y) = k\),\(\gcd(x,y)\) 函数为 \(x\) 和 \(y\) 的最大公约数。
输入格式
第一行一个整数 \(n\),接下来 \(n\) 行每行五个整数,分别表示 \(a,b,c,d,k\)。
输出格式
共 \(n\) 行,每行一个整数表示满足要求的数对 \((x,y)\) 的个数。
输入输出样例 #1
输入 #1
2
2 5 1 5 1
1 5 1 5 2
输出 #1
14
3
说明/提示
对于 \(100\%\) 的数据满足:\(1 \le n,k \le 5 \times 10^4\),\(1 \le a \le b \le 5 \times 10^4\),\(1 \le c \le d \le 5 \times 10^4\)。
思路





最后我们还要用到一个分块的思想,继续优化时间复杂的
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <string>
#include <cstring>
using namespace std;
// 定义64位整数类型(此处实际为int,若数据范围大建议改为long long)
using i64 = int;
// 宏定义换行符,加速输出
#define endl '\n';
// 全局变量:k为目标gcd值,t为测试用例数量
i64 k,t;
// mu数组存储莫比乌斯函数的前缀和(预处理后)
i64 mu[100005];
// is_prime数组标记是否为非素数(1表示非素数,0表示素数)
i64 is_prime[100005];
// prime向量存储筛选出的素数
vector<i64> prime;
/**
* @brief 预处理莫比乌斯函数值,并计算其前缀和
* 采用线性筛(欧拉筛)算法,时间复杂度O(n),n=50000
* 最终mu数组存储的是莫比乌斯函数的前缀和(便于后续区间和计算)
*/
void get_mu()
{
// 初始化mu[1] = 1(莫比乌斯函数定义:μ(1)=1)
mu[1] = 1;
// 线性筛核心:遍历2到50000的所有数
for (i64 i = 2; i <= 50000; i++)
{
// 若i未被标记为非素数,则i是素数
if(!is_prime[i]){
prime.push_back(i); // 将素数i加入素数列表
mu[i] = -1; // 素数是单个不同素因子,μ值为-1((-1)^1)
}
// 用已发现的素数prime[j]筛去i*prime[j]
for (i64 j = 0; j < prime.size(); j++)
{
// 若i*prime[j]超过50000,跳出循环(避免越界)
if(i * prime[j] > 50000)break;
// 标记i*prime[j]为非素数
is_prime[i * prime[j]] = 1;
// 若i能被prime[j]整除(即prime[j]是i的最小素因子)
if (i % prime[j] == 0)
{
mu[i * prime[j]] = 0; // i*prime[j]含平方因子(prime[j]^2),μ值为0
break; // 线性筛关键:每个数仅被最小素因子筛一次,跳出避免重复
}
// 若i不能被prime[j]整除,说明prime[j]是新素因子,μ值取反
mu[i * prime[j]] = -mu[i];
}
}
// 计算莫比乌斯函数的前缀和:mu[i] = μ(1) + μ(2) + ... + μ(i)
// 目的是快速计算区间[d, nextd]的μ值和(mu[nextd] - mu[d-1])
for (i64 i = 1; i <= 50000; i++)
{
mu[i] += mu[i - 1];
}
}
/**
* @brief 计算区间[1,n]×[1,m]中,gcd(i,j)=k的数对(i,j)的个数
* 基于数论推导:转化为互质计数问题,并用莫比乌斯函数和数论分块优化
* @param n 第一个维度的上限
* @param m 第二个维度的上限
* @return 满足条件的数对个数
*/
i64 solve(i64 n,i64 m)
{
i64 ans = 0; // 存储结果
// 数论分块:枚举d,每次处理一个区间[d, nextd],优化求和效率
// d的范围:d*k <= min(n,m)(由推导中的变量范围限制)
for (int d = 1, nextd; d * k <= min(n, m); d = nextd + 1)
{
// 计算当前分块的右边界nextd:取n/(n/d)和m/(m/d)的最小值
// 原理:对于d,使得n/d和m/d的值不变的最大d为nextd
nextd = min(n / (n / d), m / (m / d));
// 确保nextd不超过上限(d*k <= min(n,m))
nextd = min(nextd, min(n, m) / k);
// 累加当前分块的贡献:
// (n/(k*d)) * (m/(k*d)) 是满足d'=d时的计数项
// (mu[nextd] - mu[d-1]) 是区间[d, nextd]的莫比乌斯函数和(前缀和相减)
ans += (n / k / d) * (m / k / d) * (mu[nextd] - mu[d - 1]);
}
return ans;
}
int main()
{
// 关闭同步流,加速输入输出
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// 预处理莫比乌斯函数及其前缀和
get_mu();
// 读入测试用例数量
cin >> t;
i64 a, b, c, d; // 区间参数:i∈[a,b],j∈[c,d]
while (t--)
{
// 读入区间参数和目标gcd值k
cin >> a >> b >> c >> d >> k;
// 二维前缀和容斥原理:计算[a,b]×[c,d]的结果
// 公式:f(b,d) - f(b,c-1) - f(a-1,d) + f(a-1,c-1)
// 其中f(n,m)是[1,n]×[1,m]中满足条件的数对个数
cout << solve(b, d) - solve(b, c - 1) - solve(a - 1, d) + solve(a - 1, c - 1) << endl;
}
return 0;
}

浙公网安备 33010602011771号