学习笔记 ---- 莫比乌斯反演总结

概述

对于一些函数 \(f(n)\),如果很难直接求出它的值,但是容易求出其约数或倍数的\(g(n)\)和,并且\(f\)\(g\) 满足一定关系。那么可以通过莫比乌斯反演求出 \(f(n)\)

前置知识

数论分块

点这里

狄利克雷卷积

还不会,先鸽着

积性函数

\(T\) 为函数 \(f\) 的定义域。如果 \(\forall a,b \in T 且 gcd(a,b)=1\) 都有 \(f(ab) = f(a) \times f(b)\),那么称函数 \(f\) 为积性函数。

一个很好的性质是几乎所有积性函数都能用 线性筛 筛出来。

常见的积性函数有欧拉函数和莫比乌斯函数

莫比乌斯函数

定义

\(\mu(n)\) 为莫比乌斯函数,定义为:

\(\mu(n) = \begin{cases} 1 & \text{ } n=1 \\ 0 & \text{ } n含有平方因子 \\ (-1)^k & \text{ } k为n本质不同的质因子数 \end{cases}\)

性质

  1. 莫比乌斯函数是积性函数
  2. \(\sum_{d|n}\mu(d) = [n=1]\)

第二条性质非常重要,可以将一个逻辑表达式的值转化为对 \(\mu\) 函数求和。下面证明一下:

  1. \(n = 1\) 时: \(\sum_{d|n}\mu(d) = \mu(1) = 1\),显然等于右边。

  2. \(n \ne 1\) 时:我们考虑写出 \(n\) 质因数分解后的形式 \(n = p_1^{c_1} \times p_2^{c_2} \times ... \times p_k^{c_k}\)。然后如果一个因子 \(d\) 含有某个 \(p_1 \sim p_k\) 的幂次大于 \(1\),那么根据莫比乌斯函数的定义,此时 \(\mu(d) = 0\)。因此我们只需要考虑每个质因数是否出现在 \(d\) 即可。

    那么有 \(\sum_{d|n}\mu(d) = \sum_{i = 0}^{k} \binom{k}{i}(-1)^i = (1 -1)^k = 0\)

综上,得证。

补充结论:\([gcd(i, j) = 1] = \sum_{d|gcd(i, j)}\mu(d)\)

这个由上述性质可以直接得到。

求莫比乌斯函数模版:

int mu[N], prime[N], tot;
bool vis[N];
void pre() {
	mu[1] = 1; // 注意1是特殊的
	for(int i = 2; i < N; i ++ ) {
		if(!vis[i]) {prime[++ tot] = i; mu[i] = -1;} // 根据定义,质数的莫比乌斯函数为-1 
		for(int j = 1; i * prime[j] < N && j <= tot; j ++ ) {
			vis[i * prime[j]] = 1;
			if(i % prime[j] == 0) {
				mu[i * prime[j]] = 0; // 含有平方因子,莫比乌斯函数为0 
				break;
			}
			mu[i * prime[j]] = -mu[i]; // 根据积性函数的性质, mu[i * prime[j]] = mu[i] * mu[prime[j]] = -mu[i] 
		}
	} 
}

莫比乌斯反演

因子形式

\(F(n) = \sum_{d|n}f(d)\),则 \(f(n) = \sum_{d|n}\mu(\frac{n}{d})F(d)\)

证明:
\(F(d)\) 带入,那么有:
\(\sum_{d|n}\mu(\frac{n}{d})F(d)\)
\(= \sum_{d|n}\mu(\frac{n}{d})\sum_{k|d}f(k)\)
\(= \sum_{k|n}f(k)\sum_{t|\frac{n}{k}}\mu(t)\)

根据莫比乌斯函数的性质 \(2\):当 \(\frac{n}{k} = 1\) 时,右边的和式为 \(1\),此时 \(n = k\),那么贡献为 \(f(n)。\) 否则右边的和式为 \(0\)\(k\) 没有贡献。

因此结果为 \(右边=f(n)=左边\)

倍数形式

\(F(n) = \sum_{n|d}f(d)\),则 \(f(n) = \sum_{n|d}\mu(\frac{d}{n})F(d)\)

证明:
\(F(d)\) 带入,那么有:
\(\sum_{n|d}\mu(\frac{d}{n})F(d)\)
\(=\sum_{n|d}\mu(\frac{d}{n})\sum_{d|k}f(k)\)
\(=\sum_{n|k}f(k)\sum_{t|\frac{k}{n}}\mu(t)\)

只有当\(\frac{k}{n} =1\) 时,\(f(k)\) 有贡献。此时 \(k = n\),只有一个 \(f(n)\) 算入到了答案中。

因此 \(右边=f(n)=左边\)

实际上,上述两个形式并不太常用。通常情况下只用到 \([gcd(i, j) = 1] = \sum_{d|gcd(i, j)}\mu(d)\) 这一个性质就足够了。但是回归本质:如果我们发现 \(F\) 函数好求,而 \(f\) 函数不好求,并且 \(F\)\(f\) 函数满足上述两种关系之一。那么我们可以通过求出 \(F\) 来反演得到 \(f\)

例题

周期性字符串

点这里
题意:
如果一个字符串 \(s\) 可以由另一个字符串 \(t(t\ne s)\) 重复若干次得到,那么称 \(s\) 具有周期性。给定一个 \(n\),求有多少长度为 \(n\) 的周期字符串。 \(1 \leq n \leq 10^6\)

分析:

首先可以 \(dp\)。设 \(f_{i}\) 表示长度为 \(i\) 的周期性字符串个数。那么有转移 \(f_{i} = \sum\limits_{j|i}26^j - f_j\)。预处理 \(26\) 的幂次复杂度可做到 \(O(n\ln n)\)。能够通过本题,但是如果 \(n\) 开到 \(10^7\) 就容易寄。

我们考虑莫比乌斯反演。设 \(g_i\) 表示长度为 \(i\) 且非周期性字符串个数,设 \(h_i\) 表示所有长度为 \(i\) 的因数的非周期性字符串个数。那么显然 \(h_n = \sum_{d|n}g(d)\),答案就是 \(\sum_{d|n,d \ne n}g(d)\)

我们发现 \(h_i = 26^i\)。这是因为任意一个长度为 \(i\) 的字符串 \(s\) 都可以对应一个最小的长度是 \(i\) 因数的非周期字符串 \(t\)。任意一个长度为 \(i\) 因数非周期性字符串 \(t\) 也都可以通过复制拼接唯一对应一个长度为 \(i\) 的字符串 \(s\)。因此这建立了一个双射,所以二者是相等的。

然后我们只需要把所以 \(d|n,d\ne n\)\(g(d)\)\(h\) 反演出来即可。时间复杂度 \(O(\sqrt{n} \times \sqrt{n} = n)\)

CODE:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
typedef long long LL;
const LL mod = 1e9 + 7;
int n, mu[N], prime[N], tot;
bool vis[N];
LL f[N], F[N], res;
inline LL Pow(LL x, LL y) {
	LL res = 1LL, k = x;
	while(y) {
		if(y & 1) res = (res * k) % mod;
		y >>= 1;
		k = (k * k) % mod;
	}
	return res;
}
void Get_mu() {
	mu[1] = 1;
	for(int i = 2; i < N; i ++ ) {
		if(!vis[i]) {prime[++ tot] = i; mu[i] = -1;}
		for(int j = 1; j <= tot && i * prime[j] < N; j ++ ) {
			vis[i * prime[j]] = 1;
			if(i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				break;
			}
			mu[i * prime[j]] = -mu[i];
  		}
	}
}
void calc(int n) {
	for(int d = 1; d * d <= n; d ++ ) {
		if(n % d == 0) {
			F[n] = ((F[n] + 1LL * mu[d] * f[n / d] % mod) % mod + mod) % mod;
			if(d * d != n) F[n] = ((F[n] + 1LL * mu[n / d] * f[d] % mod) % mod + mod) % mod;
		}
	}
}
int main() {
	Get_mu();
	cin >> n;
	for(int i = 1; i * i <= n; i ++ ) {
		if(n % i == 0) {
			f[i] = Pow(26LL, 1LL * i);
			f[n / i] = Pow(26LL, 1LL * (n / i));
		}
	}
	for(int i = 1; i * i <= n; i ++ ) {
		if(n % i == 0 && i != n) {
			calc(i); res = (res + F[i]) % mod;
			if(i * i != n && n / i != n) calc(n / i), res = (res + F[n / i]) % mod;
		}
	}
	cout << res << endl;
	return 0;
}

互质数对

点这里
题意:
给定一个 \(n\),求 \(\sum_{i = 1}^{n}\sum_{j = 1}^{n}[gcd(i, j) = 1]\)\(n \leq 10^7\)

分析:
看到 \([gcd(i, j) = 1]\),想到莫比乌斯函数
\(\sum_{i = 1}^{n}\sum_{j = 1}^{n}[gcd(i, j) = 1]\)
\(=\sum_{i = 1}^{n}\sum_{j = 1}^{n}\sum\limits_{d|gcd(i, j)}\mu(d)\)

把枚举质因数提前

\(=\sum_{d=1}^{n}\mu(d) \times \left \lfloor \frac{n}{d} \right \rfloor \times \left \lfloor \frac{n}{d} \right \rfloor\)

处理出 \(u(i)\) 的前缀和,然后一维数论分块就行。
复杂度 \(O(\sqrt{n})\)

CODE:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e7 + 10;
int n, prime[N], tot;
bool vis[N];
LL mu[N], sum[N], res;
void Get_mu() {
	mu[1] = 1;
	for(int i = 2; i < N; i ++ ) {
		if(!vis[i]) {prime[++ tot] = i; mu[i] = -1;}
		for(int j = 1; j <= tot && 1LL * i * prime[j] < 1LL * N; j ++ ) {
			vis[i * prime[j]] = 1;
			if(i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				break;
			}
			mu[i * prime[j]] = -mu[i];
		}
	}
	for(int i = 1; i < N; i ++ ) sum[i] = sum[i - 1] + mu[i];
}
int main() {
	Get_mu();
	cin >> n;
	int l = 1, r;
	while(l <= n) {
		r = n / (n / l);
		res = res + (sum[r] - sum[l - 1]) * (1LL * n / l) * (1LL * n / l);
		l = r + 1;
	}
	cout << res << endl;
	return 0;
}

[NOI2010D1T1]能量采集

点这里
题意:给你 \(n, m\),求 \(\sum_{i = 1}^{n}\sum_{j = 1}^{m}2 \times gcd(i, j) - 1\)\(1 \leq n, m \leq10^5\)

分析:
看到 \(gcd(i, j)\),可以考虑莫比乌斯函数
枚举 \(gcd\)
\(\sum_{i = 1}^{n}\sum_{j = 1}^{m}2 \times gcd(i, j) - 1\)
\(=\sum_{i = 1}^{n}\sum_{j =1}^{m}\sum_{d=1}^{min(n, m)}(2 \times d - 1) \times ([gcd(i, j) = d])\)

提前枚举 \(gcd\)

\(=\sum_{d = 1}^{min(n,m)}(2\times d- 1) \times \sum_{i = 1}^{n}\sum_{j = 1}^{m}[gcd(i, j) = d]\)

右边的两个求和符号上指标同时除以 \(d\),变为枚举 \(d\) 的倍数:

\(=\sum_{d = 1}^{min(n, m)}(2 \times d - 1) \times \sum_{i = 1}^{\left \lfloor \frac{n}{d} \right \rfloor} \sum_{j = 1}^{\left \lfloor \frac{m}{d} \right \rfloor}[gcd(i, j) = 1]\)

\(=\sum_{d = 1}^{min(n, m)}(2 \times d - 1)\sum_{i = 1}^{\left \lfloor \frac{n}{d} \right \rfloor}\sum_{j = 1}^{\left \lfloor \frac{m}{d} \right \rfloor}\sum_{k|gcd(i, j)}\mu(k)\)
\(=\sum_{d = 1}^{min(n, m)}(2\times d - 1)\sum_{k = 1}^{min(\left \lfloor \frac{n}{d} \right \rfloor, \left \lfloor \frac{m}{d} \right \rfloor)}\mu(k) \times \left \lfloor \frac{n}{dk} \right \rfloor \times \left \lfloor \frac{m}{dk} \right \rfloor\)

然后考虑对于 \(d\) 而言, \(\left \lfloor \frac{n}{d} \right \rfloor\)\(\left \lfloor \frac{m}{d} \right \rfloor\) 成块状分布,可以二维分块得到 \(n' = \left \lfloor \frac{n}{d} \right \rfloor\)\(m' = \left \lfloor \frac{m}{d} \right \rfloor\)。对于 \(n'\)\(m'\) 分别相同的的一段内,右边和式的贡献是相同的,变成了 \(\sum_{k = 1}^{min(n', m')}\mu(k) \times \left \lfloor \frac{n'}{k} \right \rfloor \times \left \lfloor \frac{m'}{k} \right \rfloor\)。也可以再二维整除分块一次,总复杂度就是 \(O(\sqrt{n} \times \sqrt{n} = n)\) 的。

实际上复杂度还能做到 \(O(\sqrt{n})\),这个后边会讲。

CODE:

// 整除套整除分块 好像能做到 O(n) 
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int n, m, prime[N], tot;
LL mu[N], sum[N], res;
bool vis[N];
void Get_mu() {
	mu[1] = 1;
	for(int i = 2; i < N; i ++ ) {
		if(!vis[i]) {prime[++ tot] = i; mu[i] = -1;}
		for(int j = 1; j <= tot && i * prime[j] < N; j ++ ) {
			vis[i * prime[j]] = 1;
			if(i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				break;
			}
			mu[i * prime[j]] = -mu[i]; // 积性函数性质 
		}
	}
	for(int i = 1; i < N; i ++ ) sum[i] = sum[i - 1] + mu[i];
} 
LL calc(int nt, int mt) {
	int t = min(nt, mt);
	int l = 1, r; LL res = 0;
	while(l <= t) {
		r = min({t, nt / (nt / l), mt / (mt / l)});
		res = res + 1LL * (nt / l) * (mt / l) * (sum[r] - sum[l - 1]);
		l = r + 1;
	}
	return res;
}
int main() {
	scanf("%d%d", &n, &m);
	Get_mu();
	int t = min(n, m);
	int l = 1, r;
	while(l <= t) {
		r = min({t, n / (n / l), m / (m / l)});
		res = res + calc(n / l, m / l) * (1LL * (l + r) * (r - l + 1) - 1LL * (r - l + 1));
		l = r + 1;
	}
	printf("%lld\n", res);
	return 0;
}

BZOJ2820 YY的GCD

点这里
题意:
\(T\) 次询问,每次给你 \(n,m\),问\(\sum_{i = 1}^{n}\sum_{j = 1}^{m}[gcd(i, j) = p],p代表质数\)
\(T \leq 10^4,1 \leq n,m \leq 10^7\)

分析:
首先看到 逻辑表达式的值 和 \(gcd(i, j)\),考虑莫反。
如果我们定义 \(f(x) = \begin{cases} 1 & \text{ } x\in P \\ 0 & \text{ } x \notin P \end{cases}\)\(P\) 代表素数集合。那么所要求的就是:
\(\sum_{i=1}^{n}\sum_{j = 1}^{m}f(gcd(i, j))\)
\(=\sum_{d=1}^{min(n, m)}f(d) \sum_{i = 1}^{n}\sum_{j = 1}^{m}[gcd(i, j) = d]\)
\(=\sum_{d = 1}^{min(n, m)}f(d)\sum_{i = 1}^{\left \lfloor \frac{n}{d} \right \rfloor}\sum_{j = 1}^{\left \lfloor \frac{m}{d} \right \rfloor}[gcd(i, j) = 1]\)
\(=\sum_{d = 1}^{min(n, m)}f(d)\sum_{i = 1}^{\left \lfloor \frac{n}{d} \right \rfloor}\sum_{j = 1}^{\left \lfloor \frac{m}{d} \right \rfloor}\sum_{k|gcd(i, j)}\mu(k)\)
\(=\sum_{d = 1}^{min(n, m)}f(d)\sum_{k = 1}^{min(\left \lfloor \frac{n}{d} \right \rfloor,\left \lfloor \frac{m}{d} \right \rfloor)}\mu(k) \times \left \lfloor \frac{n}{dk} \right \rfloor \times \left \lfloor \frac{m}{dk} \right \rfloor\)

首先 \(f\) 数组的求解是简单的,可以在 \(O(N)\) 的复杂度内预处理出来。然后我们可以使用上一题的思路,二次分块,但是那样每次询问复杂度是 \(O(n)\) 的,总复杂度就变成 \(O(Tn)\),无法接受。

怎么办呢?我们好像已经化到了最简,无法进一步优化了。

这里有一个很通用的技巧,也算一个很重要的套路:

当我们需要二次分块时,一般第二个求和符号后面的分母都是两个变量的乘积。尝试把乘积提前枚举,并将式子变形。往往能得到一个可以预处理的函数,使得复杂度少一个 \(\sqrt{n}\)

我们来尝试一下:

首先理解一下式子的含义:相当于把所有满足 \(dk \leq min(n, m)\) 的数对 \((d, k)\) 都贡献一个 \(f(d) \times \mu(k) \times \left \lfloor \frac{n}{dk} \right \rfloor \left \lfloor \frac{m}{dk} \right \rfloor\) 到答案中。

\(dk = T\),那么有:
\(\sum_{d = 1}^{min(n, m)}f(d)\sum_{k = 1}^{min(\left \lfloor \frac{n}{d} \right \rfloor,\left \lfloor \frac{m}{d} \right \rfloor)}\mu(k) \times \left \lfloor \frac{n}{dk} \right \rfloor \times \left \lfloor \frac{m}{dk} \right \rfloor\)
\(=\sum_{T = 1}^{min(n, m)} \left \lfloor \frac{n}{T} \right \rfloor \times \left \lfloor \frac{m}{T} \right \rfloor \sum_{d|T}f(d) \times \mu(\frac{T}{d})\)

\(F(T) = \sum_{d|T}f(d) \times \mu(\frac{T}{d})\)。那么原式变成了

\(\sum_{T = 1}^{min(n, m)}\left \lfloor \frac{n}{T} \right \rfloor \times \left \lfloor \frac{m}{T} \right \rfloor \times F(T)\)

我们发现 \(F(T)\) 是可以在 \(O(N\ln N)\) 的复杂度内预处理出来的,因此每次询问只需要一次二维数论块就可以了。

为什么这样做复杂度就能够优化呢?
我们考虑变形前的式子中 两个求和符号都是与每次给定的 \(n\)\(m\) 相关的,这样就无法预处理。而如果我们让第一个求和符号枚举乘积,第二个求和符号枚举因子,那么第二个求和就只与乘积\(T\)的大小有关,与 \(n, m\) 无关了。因此一般可以预处理。

实际上本题的预处理可以做到线性。一些较难的题目也会在预处理上做文章,需要线性的做法才能通过。而 \(F\) 数组大多都是两个函数的 卷积,可以根据卷积的一些性质来求。

总复杂度就是 \(O(N \ln N + T\sqrt{n})\),其中 \(N\)\(n\) 的上限。

CODE:

// 好像会线性了? 
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e7 + 10;
LL f[N], sum[N];
int T, n, m, mu[N];
int prime[N], tot;
bool vis[N];
void Get_mu() {
	mu[1] = 1;
	for(int i = 2; i < N; i ++ ) {
		if(!vis[i]) {prime[++ tot] = i; mu[i] = -1; f[i] = 1LL;}
		for(int j = 1; j <= tot && 1LL * i * prime[j] < 1LL * N; j ++ ) {
			vis[i * prime[j]] = 1;
			if(i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				f[i * prime[j]] = mu[i];
				break;
			}
			mu[i * prime[j]] = -mu[i];
			f[i * prime[j]] = -f[i] + mu[i];
		}
	}
	for(int i = 1; i < N; i ++ ) sum[i] = sum[i - 1] + f[i];
}
LL calc(int n, int m) {
	int l = 1, r; LL res = 0;
	int t = min(n, m);
	while(l <= t) {
		r = min({t, n / (n / l), m / (m / l)});
		res = res + 1LL * (n / l) * (m / l) * (sum[r] - sum[l - 1]);
		l = r + 1;
	}
	return res;
}
int main() {
	Get_mu();
	scanf("%d", &T);
	while(T -- ) {
		scanf("%d%d", &n, &m);
		printf("%lld\n", calc(n, m));
	}
	return 0;
}

[SDOI2015] 约数个数和

点这里
题意:设 \(d(x)\)\(x\) 的因数个数。\(T\) 次询问,每次给定 \(n, m\),求 \(\sum_{i = 1}^{n}\sum_{j = 1}^{m}d(i \times j)\)
\(T \leq 5 \times 10^4,1\leq n, m \leq 5 \times 10^4\)

首先有一个对本题而言非常重要的结论:
\(d(x \times y) = \sum_{i|x}\sum_{j|y}[gcd(i, j) = 1]\)

证明一下:
\(P(n)\) 表示 \(n\) 质因数分解后的质因子集合。
那么设 \(P(x \times y) = \left \{ p_1,p_2,...,p_k\right \}\)

\(x = p_1^{c_1} p_2^{c_2}...p_k^{c_k}\)
\(y = p_1^{t_1}p_2^{t_2}...p_k^{t_k}\)

那么很显然 \(x \times y\) 的因子数量为 \(\prod_{i=1}^{k}(c_i+t_i+1)\)
然后考虑 \(\sum_{i|x}\sum_{j|y}[gcd(i, j) = 1]\) 什么时候会对答案有贡献。
需要对于 \(p_1 \sim p_k\)\(k\) 个质因数,\(x\) 不含 或者 \(y\) 不含。

对于一个素数\(p_i\)

  1. \(x\) 不含,\(y\) 含。这种情况有 \(t_i\)
  2. \(x\) 含, \(y\) 不含。这种情况有 \(c_i\)
  3. \(x\) 不含,且 \(y\) 不含。这种情况有 \(1\) 种。

因此每一个 \(i\) 的方案数都是都是 \(c_i + t_i + 1\) 种。由于是分步。需要把它们乘起来,总方案数就是 \(\prod_{i=1}^{k}(c_i + t_i + 1)\)。每一种方案都唯一对应了一个数对 \((p, q)\),满足 \(p|x,q|y\)\(gcd(p, q) = 1\)

那么原问题就变成求 \(\sum_{i = 1}^{n}\sum_{j=1}^{m}\sum_{p|i}\sum_{q|j}[gcd(p,q )=1]\)
\(=\sum_{i = 1}^{n}\sum_{j=1}^{m}\sum_{p|i}\sum_{q|j}\sum_{k|gcd(p, q)}\mu(k)\)

变成先枚举 \(k\),再枚举 \(p,q\),最后枚举 \(i, j\) 的形式

\(=\sum_{k = 1}^{min(n, m)}\mu(k)\sum_{p = 1}^{\left \lfloor \frac{n}{k} \right \rfloor}\sum_{q=1}^{\left \lfloor \frac{m}{k} \right \rfloor} \left \lfloor \frac{n}{kp} \right \rfloor \times \left \lfloor \frac{m}{kq} \right \rfloor\)

\(=\sum_{k=1}^{min(n, m)}\mu(k) \times \sum_{p=1}^{\left \lfloor \frac{n}{k} \right \rfloor} \left \lfloor \frac{n}{kp} \right \rfloor \times \sum_{q=1}^{\left \lfloor \frac{m}{k} \right \rfloor}\left \lfloor \frac{m}{kq} \right \rfloor\)

\(f(n) = \sum_{i=1}^{n}\left \lfloor \frac{n}{i} \right \rfloor\)

那么原式就是

\(=\sum_{k=1}^{min(n, m)}\mu(k)\times f(\left \lfloor \frac{n}{k} \right \rfloor) \times f(\left \lfloor \frac{m}{k} \right \rfloor)\)

然后数论分块预处理 \(f\),每次询问一维数论分块求出答案即可。
时间复杂度 \(O(n\sqrt{n} + T \sqrt{n})\)

CODE:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e4 + 10;
int T, n, m;
int mu[N], prime[N], tot, sum[N];
LL S[N];
bool vis[N];
void get_mu() {
	mu[1] = 1;
	for(int i = 2; i < N; i ++ ) {
		if(!vis[i]) {prime[++ tot] = i, mu[i] = -1;}
		for(int j = 1; j <= tot && i * prime[j] < N; j ++ ) {
			vis[i * prime[j]] = 1;
			if(i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				break;
			}
			mu[i * prime[j]] = -mu[i];
		}
	}
	for(int i = 1; i < N; i ++ ) sum[i] = sum[i - 1] + mu[i];
}
void getS(int n) {
	int l = 1, r; 
	while(l <= n) {
		r = n / (n / l);
		S[n] = (S[n] + 1LL * (r - l + 1) * (n / l));
		l = r + 1;
	}
}
inline LL calc(int n, int m) {
	int t = min(n, m);
	int l = 1, r; LL res = 0;
	while(l <= t) {
		r = min({t, n / (n / l), m / (m / l)});
		res = res + S[n / l] * S[m / l] * 1LL * (sum[r] - sum[l - 1]);
		l = r + 1;
	}
	return res;
}
int main() {
	get_mu();
	for(int i = 1; i < N; i ++ ) getS(i);
	scanf("%d", &T);
	while(T -- ) {
		scanf("%d%d", &n, &m);
		printf("%lld\n", calc(n, m));
	}
	return 0;
}

BZOJ4407 于神之怒加强版

点这里
题意:
给你 \(T,k\)\(T\)次询问,每次询问给定 \(n,m\),求 \(\sum_{i=1}^{n}\sum_{j=1}^{m}gcd(i, j)^k \ mod \ 10^9+7\)
\(1 \leq T \leq 2 \times 10^3\)\(1 \leq n, m \leq 5 \times 10^6\)

分析:
看到 \(gcd\),还是想到枚举 \(gcd\),然后乘一个逻辑表达式。

\(\sum_{i=1}^{n}\sum_{j=1}^{m}gcd(i, j)^k \ mod \ 10^9+7\)
\(=\sum_{d=1}^{min(n,m)}d^k\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i, j) = d]\)

右边的两个求和老演员了。

\(=\sum_{d=1}^{min(n, m)}d^k \sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor} \sum_{j=1}^{\left \lfloor \frac{m}{d} \right \rfloor}[gcd(i, j)=1]\)
\(=\sum_{d=1}^{min(n, m)}d^k \sum_{t = 1}^{min(\left \lfloor \frac{n}{d} \right \rfloor, \left \lfloor \frac{m}{d} \right \rfloor)}\mu(t) \times \left \lfloor \frac{n}{dt} \right \rfloor \times \left \lfloor \frac{m}{dt} \right \rfloor\)

\(=\sum_{T=1}^{min(n, m)} \left \lfloor \frac{n}{T} \right \rfloor \times \left \lfloor \frac{m}{T} \right \rfloor \sum_{d|T} d^k \times \mu(\frac{T}{d})\)

\(F(T) = \sum_{d|T}d^k \times \mu(\frac{T}{d})\)

\(=\sum_{T=1}^{min(n, m)} \left \lfloor \frac{n}{T} \right \rfloor \times \left \lfloor \frac{m}{T} \right \rfloor \times F(T)\)

求出 \(F(T)\) 的前缀和每次询问一维数论分块就可以了。
时间复杂度 \(O(n\log n + T\sqrt{n})\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e6 + 10;
const LL mod = 1e9 + 7;
int T, n, m, tot;
bool vis[N];
LL k, mu[N], prime[N], f[N], val[N];
LL sum[N];
inline LL Pow(LL x, LL y) {
	LL res = 1LL, k = x;
	while(y) {
		if(y & 1) res = (res * k) % mod;
		y >>= 1;
		k = (k * k) % mod;
	}
	return res;
}
void pre() {
	for(int i = 1; i < N; i ++ ) val[i] = Pow(1LL * i, k);
	mu[1] = 1;
	for(int i = 2; i < N; i ++ ) {
		if(!vis[i]) {prime[++ tot] = i; mu[i] = -1;}
        for(int j = 1; i * prime[j] < N && j <= tot; j ++ ) {
        	vis[i * prime[j]] = 1;
        	if(i % prime[j] == 0) {
        		mu[i * prime[j]] = 0;
        		break;
			}
			mu[i * prime[j]] = -mu[i];
		}
	}
	for(int i = 1; i < N; i ++ ) {
		for(int j = 1; j * i < N; j ++ ) {
			f[i * j] = (f[i * j] + mu[i] * val[j] % mod) % mod;
		}
	}
	for(int i = 1; i < N; i ++ ) sum[i] = (sum[i - 1] + f[i]) % mod;
}
LL calc(int n, int m) {
	LL res = 0;
	int l = 1, r, t = min(n, m);
	while(l <= t) {
		r = min({n / (n / l), m / (m / l), t});
		res = (res + ((sum[r] - sum[l - 1]) % mod + mod) % mod * 1LL * (n / l) % mod * (m / l) % mod) % mod;
		l = r + 1;
	}
	return res;
}
int main() {
	scanf("%d%lld", &T, &k);
	pre();
	while(T -- ) {
		scanf("%d%d", &n, &m);
		printf("%lld\n", calc(n, m));
	}
	return 0;
}

【BZOJ2693】jzptab最小公倍数之和

题意:
给你一个 \(T\)\(T\)组询问,每次给定一个 \(n,m\)。求\(\sum_{i=1}^{n}\sum_{j=1}^{m}lcm(i,j)\)。答案对 \(10^8 +9\) 取模。
\(1\leq T \leq 10^4\)\(1 \leq n,m \leq 10^7\)

分析:
我们都知道 \(lcm(i, j) = \frac{i \times j}{gcd(i, j)}\)
所以有
\(\sum_{i=1}^{n}\sum_{j=1}^{m}lcm(i,j)\)
\(=\sum_{i=1}^{n}\sum_{j=1}^{m}\frac{i\times j}{gcd(i, j)}\)
\(=\sum_{d=1}^{min(n, m)}\frac{1}{d} \sum_{i = 1}^{n}\sum_{j=1}^{m}i\times j \times [gcd(i, j) = d]\)
\(=\sum_{d=1}^{min(n, m)}\frac{1}{d}\sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor}\sum_{j=1}^{\left \lfloor \frac{m}{d} \right \rfloor}(id) \times (jd) \times [gcd(i, j) = 1]\)
\(=\sum_{d=1}^{min(n, m)}d\sum_{i = 1}^{\left \lfloor \frac{n}{d} \right \rfloor}\sum_{j = 1}^{\left \lfloor \frac{m}{d} \right \rfloor}i \times j \times \sum_{k|gcd(i, j)}\mu(k)\)
\(=\sum_{d=1}^{min(n, m)}d\sum_{k = 1}^{min(\left \lfloor \frac{n}{d} \right \rfloor, \left \lfloor \frac{m}{d} \right \rfloor)}\mu(k)\sum_{i = 1}^{\left \lfloor \frac{n}{dk} \right \rfloor}\sum_{j = 1}^{\left \lfloor \frac{m}{dk} \right \rfloor}(ik) \times (jk)\)
\(=\sum_{d =1}^{min(n, m)}d\sum_{k = 1}^{min(\left \lfloor \frac{n}{d} \right \rfloor, \left \lfloor \frac{m}{d} \right \rfloor)}\mu(k) \times k^2 \times f(\left \lfloor \frac{n}{dk} \right \rfloor) \times f(\left \lfloor \frac{m}{dk} \right \rfloor)\)

其中 \(f(n) = \sum_{i=1}^{n}i = \frac{n \times (n + 1)}{2}\)

然后设 \(T = dk\)

\(=\sum_{T=1}^{min(n, m)}f(\left \lfloor \frac{n}{T} \right \rfloor) \times f(\left \lfloor \frac{m}{T} \right \rfloor) \sum_{d|T} d^2 \times (\frac{T}{d}) \times \mu(d)\)
\(=\sum_{T=1}^{min(n, m)}f(\left \lfloor \frac{n}{T} \right \rfloor) \times f(\left \lfloor \frac{m}{T} \right \rfloor) \sum_{d|T} d \times T \times \mu(d)\)

\(F(T) = T\times \sum_{d|T}d\times \mu(d)\)
\(F(T)\) 可以在\(O(n \ln n)\) 的复杂度内预处理,但这样还是会超时。

其实我们可以对于每个 \(T\),求出 \(F(T) = \sum_{d|T}d\times \mu(d)\),然后再乘 \(T\) 就可以了。
接着发现 \(F\) 是积性函数,因为对于互质的 \(n, m\)\(F(nm) = F(n) \times F(m)\)。这是因为 \(n, m\) 互质,所以任意一个 \(nm\) 的因数\(t\) 都能表示成\(n\) 的一个因数 \(p\)\(m\) 的一个因数 \(q\)的形式。而由于莫比乌斯函数是积性函数,因此 \(t\times\mu(t) = p \times q \times \mu(p)\times \mu(q)\)。证明了 \(F\) 是积性函数。

我们考虑在线性筛的过程中求出 \(F\) 函数:
对于当前 \(i\) 和素数 \(p\)

  1. \(i \% p \ne 0\),那么 \(F[i \times p] = F[i] \times F[p]\)。因为 \(i,p\) 互质
  2. \(i \% p =0\),那么\(F[i \times p] = F[i]\)。分枚举因数 \(d\) 时包不包括新加进来的 \(p\) 两种情况考虑:如果不包括,那就是 \(F[i]\)。如果包括,那么此时 \(d\)\(p\) 的幂次一定大于 \(1\)\(\mu(d) = 0\),没有贡献。
  3. 对于质数 \(p\)\(F[p] = 1 - p\)

这样就能线性求出 \(F\) 了。然后每次询问一维数论分块就好了。
复杂度 \(O(n + T\sqrt{n})\)

CODE:

// 两次分块可以把复杂度做到O(n),但是如果多测就寄了
// 一般套路是考虑把第二次分块里的分母(不换元)提出来枚举,然后里面的函数观察发现是积性函数可以提前预处理 
// 然后就可以变成一次分块了。时间复杂度O(T√n) 
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1e8 + 9;
const int N = 1e7 + 10;
int T, n, m, mu[N], prime[N], tot;
bool vis[N];
LL F[N];
void pre() {
	mu[1] = 1; F[1] = 1;
	for(int i = 2; i < N; i ++ ) {
		if(!vis[i]) {prime[++ tot] = i; mu[i] = -1; F[i] = (mod + 1LL - i);}
		for(int j = 1; 1LL * i * prime[j] < 1LL * N && j <= tot; j ++ ) {
			vis[i * prime[j]] = 1;
			if(i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				F[i * prime[j]] = F[i];
				break;
			}
			mu[i * prime[j]] = -mu[i];
			F[i * prime[j]] = F[i] * F[prime[j]] % mod;
		}
	}
	for(int i = 1; i < N; i ++ ) {
		F[i] = F[i] * 1LL * i % mod;
		F[i] = (F[i] + F[i - 1]) % mod;
	}
}
inline LL f(int n) {
	return 1LL * (n + 1LL) * n / 2 % mod;
}
LL calc(int n, int m) {
	int t = min(n, m);
	int l = 1, r; LL res = 0;
	while(l <= t) {
		r = min({n / (n / l), m / (m / l), t});
		res = ((res + f(n / l) * f(m / l) % mod * (F[r] - F[l - 1]) % mod) % mod + mod) % mod;
		l = r + 1;
	}
	return res;
}
int main() {
	pre();
	scanf("%d", &T);
	while(T -- ) {
		scanf("%d%d", &n, &m);
		printf("%lld\n", calc(n, m));
	}
	return 0;
}

[bzoj3529-Sdoi2014] 数表

点这里
题意:
\(f(n) = \sum_{d|n}d\)\(Q\) 次询问,每次给你 \(n, m, a\),求 \(\sum_{i = 1}^{n}\sum_{j = 1}^{m}f(gcd(i, j))[f(gcd(i, j)) \leq a]\)
\(1 \leq n, m \leq 10^5\)\(1 \leq Q \leq 2 \times 10^4\)

分析:
\([f(gcd(i, j)) \leq a]\) 的限制不好做。
我们考虑设一个新函数 \(g(n) = \begin{cases} f(n) & \text{} f(n)\leq a \\ 0 & \text{} f(n) > a \end{cases}\)
那么式子变成了 \(\sum_{i = 1}^{n}\sum_{j = 1}^{m}g(gcd(i, j))\)
由于 \(gcd\) 的取值比较小,我们可以求出所有的 \(f(n)\)。然后按照 \(f(n)\) 从小到大的顺序把 \(n\) 排序,把询问按照 \(a\) 从小到大的顺序排序后,我们就可以每次加入一些 \(n\),把他们的 \(g(n)\)\(0\) 变为 \(f(n)\)

然后考虑上面的式子怎么求:
\(\sum_{i = 1}^{n}\sum_{j = 1}^{m}g(gcd(i, j))\)
\(=\sum_{d=1}^{min(n, m)}g(d)\sum_{i = 1}^{n}\sum_{j = 1}^{m}[gcd(i, j) = d]\)
\(=\sum_{d=1}^{min(n, m)}g(d)\sum_{i = 1}^{\left \lfloor \frac{n}{d} \right \rfloor}\sum_{j = 1}^{\left \lfloor \frac{m}{d} \right \rfloor}[gcd(i, j) = 1]\)
\(=\sum_{d=1}^{min(n, m)}g(d)\sum_{k = 1}^{min(\left \lfloor \frac{n}{d} \right \rfloor,\left \lfloor \frac{m}{d} \right \rfloor)}\mu(k) \times \left \lfloor \frac{n}{dk} \right \rfloor \times \left \lfloor \frac{m}{dk} \right \rfloor\)
\(=\sum_{T = 1}^{min(n, m)} \left \lfloor \frac{n}{T} \right \rfloor \times \left \lfloor \frac{m}{T} \right \rfloor \sum_{d|T}g(d)\times \mu(\frac{T}{d})\)
\(F(T) = \sum_{d|T}g(d) \times \mu(\frac{T}{d})\)

那么我们需要维护出来 \(F\) 的前缀和。
每次新加入一个 \(d\) 就把它的所有倍数 \(T\) 加上\(f(d) \times \mu(\frac{T}{d})\)。那么我们需要一个单点修改,区间求和的数据结构。树状数组就可以。时间复杂度是 调和级数 加上树状数组的复杂度。 \(O(n\log^2 n + Q\sqrt{n}\log n)\)
CODE:

#include<bits/stdc++.h>
#define int long long
using namespace std; // 首先容易求出 f(d), 然后离线就好了 
const int N = 1e5 + 10;
const int mod = (1LL << 31);
int n, m, q;
int mu[N], prime[N], tot, idx[N];
bool vis[N];
int f[N], F[N], c[N], ans[N];
struct qq {
	int n, m, v, id;
}qc[N];
int lowbit(int x) {return x & -x;}
void add(int x, int y) {
	for(; x < N; x += lowbit(x)) c[x] = (c[x] + y) % mod;
}
int ask(int x) {
	int res = 0;
	for(; x; x -= lowbit(x)) res = (res + c[x]) % mod;
	return res;
}
void pre() {
	mu[1] = 1;
	for(int i = 2; i < N; i ++ ) {
		if(!vis[i]) {prime[++ tot] = i; mu[i] = -1;}
		for(int j = 1; i * prime[j] < N && j <= tot; j ++ ) {
			vis[i * prime[j]] = 1;
			if(i % prime[j] == 0) {
				mu[i * prime[j]] = 0;
				break;
			}
	    	mu[i * prime[j]] = -mu[i];
		}
	}
	for(int i = 1; i < N; i ++ ) {
		for(int j = 1; j * i < N; j ++ ) {
			f[j * i] = (f[j * i] + (i)) % mod;
		}
	}
}
bool cmp(qq x, qq y) {
	return x.v < y.v;
}
bool cmpf(int x, int y) {
	return f[x] < f[y];
}
void Add(int x) { // 加入下标为x的 
	for(int i = 1; i * x < N; i ++ ) {
		add(i * x, (f[x] * mu[i] + mod) % mod);
	}
}
int calc(int n, int m) {
	int t = min(n, m);
	int l = 1, r; int res = 0;
	while(l <= t) {
		r = min({n / (n / l), m / (m / l), t});
		res = (res + (1LL * ((ask(r) - ask(l - 1)) % mod) * (n / l) % mod * (m / l) % mod) + mod) % mod;
		l = r + 1;
	}
	return res;
} 
signed main() {
	pre();
	scanf("%lld", &q);
	for(int i = 1; i <= q; i ++ ) {
		int v; scanf("%lld%lld%lld", &n, &m, &v);
		qc[i] = (qq) {n, m, v, i};
	}
	sort(qc + 1, qc + q + 1, cmp);
	for(int i = 1; i < N; i ++ ) idx[i] = i;
	sort(idx + 1, idx + N, cmpf);
	int now = 1;
	for(int i = 1; i <= q; i ++ ) {
		while(now < N && f[idx[now]] <= qc[i].v) {
			Add(idx[now]);
			now ++;
		}
		ans[qc[i].id] = calc(qc[i].n, qc[i].m);
	}
	for(int i = 1; i <= q; i ++ ) printf("%lld\n", ans[i]);
	return 0;
}

练习题

51nod 1675 序列变换

点这里
题意:
给你两个长度为 \(n\) 的序列 \(a,b\),求有多少对 \((x,y)\),满足以下两个条件:

  1. \(gcd(x, y) = 1\)
  2. \(a_{b_x} = b_{a_y}\)

\(1 \leq n \leq 10^5,1\leq a_i,b_i \leq n\)

BZOJ3561 DZY Loves Math VI

点这里
题意:
给定正整数 \(n,m\),求:\(\sum_{i = 1}^{n}\sum_{j = 1}^{n}lcm(i, j)^{gcd(i, j)}\)
\(1 \leq n,m \leq 5 \times 10^5\)

[bzoj3309]DZY Loves Math

点这里
题意:
对于正整数 \(n\),定义 \(f(n)\)\(n\) 所含质因子的的最大幂指数。特别的 \(f(1) = 0\)
\(T\) 组询问,每次给定 \(a,b\),求 \(\sum_{i = 1}^{a}\sum_{j = 1}^{b}f(gcd(i, j))\)
\(1 \leq T \leq 10^4,1\leq a,b \leq 10^7\)

[bzoj4816--Sdoi2017] 数字表格

点这里
题意:
\(f(n)\) 表示斐波那契第 \(n\) 项。
\(T\) 组询问,每次给定 \(n,m\)。求 \(\prod_{i = 1}^{n}\prod_{j = 1}^{m}f[gcd(i, j)]\)。答案对\(10^9 + 7\) 取模。
\(1 \leq T \leq 1000,1\leq n, m \leq 10^6\)

[hdoj4746]Mophues

点这里
题意:
\(Q\) 组询问,每次给你 \(n, m, p\)。求 \(\sum_{i = 1}^{n} \sum_{j = 1}^{m}[gcd(i, j) \leq p]\)
\(1 \leq Q \leq 5 \times 10^3, 2 \leq n,m,p \leq 5 \times 10^5\)

技巧总结

  1. 遇到与 \(gcd\) 有关的式子可以考虑莫比乌斯反演
  2. 把枚举 \(gcd\) 的式子提前是常见思路
  3. 遇到需要二次分块或者 分母是两个变量乘积 的情况,可以考虑交换枚举顺序,先枚举乘积。
  4. 遇到对 \(gcd\) 的大小有限制的题目可以考虑离线,把 \(gcd\) 由小到大依次加入。
posted @ 2026-09-12 20:50  Czl2007  阅读(5)  评论(0)    收藏  举报