【题解】洛谷P1390 公约数的和

洛谷P1390 公约数的和

题意简述

给定 \(n\),求

\[\sum_{i = 1}^n \sum_{j = i + 1}^n \gcd(i, j) \]

其中 \(\gcd(i, j)\) 表示 \(i\)\(j\) 的最大公约数。

  • 对于 \(40\%\) 的数据,保证 \(n \leq 2 \times 10^3\)
  • 对于 \(100\%\) 的数据,保证 \(2 \leq n \leq 2 \times 10^6\)

思路

暴力(40pts)

复杂度 $ O(n^2) $

\(Code\)

#include <bits/stdc++.h>
using namespace std;
long long ans;
long long My_gcd(int a, int b) {
	return (b == 0) ? a : My_gcd(b, a % b);
}
inline int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + ch - 48;
		ch = getchar();
	}
	return x * f;
}
inline void write(int x, bool is_endl) { //is_endl: 必须填写此布尔参数
	if (x < 0) {
		putchar('-');
		x = -x;
	}
	if (x > 9)
		write(x / 10, false);
	putchar(x % 10 + '0');
	if (is_endl)
		putchar('\n');
}
int main() {
	int n = read();
	for (int i = 1; i <= n; i++) {
		for (int j = i + 1; j <= n; j++) {
			ans += My_gcd(i, j);
		}
	}
	write(ans, true);
	return 0;
}

数学优化

前置知识:最大公约数 , 容斥原理

优化思路

考虑将 $ O(n^2) $ 的复杂度优化到 $ O(n\log n) $,则此复杂度可以接受 2e6 的数据量。

  • 统计所有 \(\gcd(i, j) = d\) 的对数,然后乘 \(d\) ,最后求和。
  • 使用容斥原理来计算每个 \(d\) 对的个数。

数学实现

不妨设 \(f(d)\) 为满足 \(\gcd(i, j) = d\)\(i < j\) 的对数,那么我们可以:

  1. 计算 \(g(d)\) :所有满足 \(i < j\)\(d \ \vert \ i\)\(d \ \vert \ j\) 的对数。

\(k = \lfloor \frac{n}{d} \rfloor\) ,则 \(g(d) = \frac{k(k - 1)}{2}\)

  1. 容斥处理 :从大到小处理每个 \(d\) ,减去所有 \(d\) 的倍数 \(m\) 对应的 \(f(m)\) ,从而得到 \(f(d) = g(d) - \sum_{m > d, m \ \vert \ d} f(m)\)

  2. 求和 :总和为 \(\sum^{n}_{d = 1} d \times f(d)\)

初始化复杂度 \(O(n)\) ,容斥处理总次数为 $ O(n\log n) $,可以 AC 此题。

\(Code\)

//C++14 (GCC 9) O2, 240ms
//Powered by WaymingDev
#include <bits/stdc++.h>
using namespace std;
long long ans;
inline int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + ch - 48;
		ch = getchar();
	}
	return x * f;
}
inline void write(int x, bool is_endl) { //is_endl: 必须填写此布尔参数
	if (x < 0) {
		putchar('-');
		x = -x;
	}
	if (x > 9)
		write(x / 10, false);
	putchar(x % 10 + '0');
	if (is_endl)
		putchar('\n');
}
int main() {
	int n = read();
	vector<long long> cnt(n + 2, 0);  // 用于存储每个 d 的对数
	for (int d = 1; d <= n; d++) {
		long long k = n / d;
		cnt[d] = k * (k - 1) / 2;
	}
	for (int d = n; d >= 1; --d) {
		for (int m = 2 * d; m <= n; m += d) {
			cnt[d] -= cnt[m];  // 容斥处理
		}
		ans += (long long)d * cnt[d];
	}
	write(ans, true);
	return 0;
}

推荐题目

双倍经验↑↑↑

\[The \ End \]

posted @ 2025-08-12 16:01  WaymingDev  阅读(8)  评论(0)    收藏  举报