【题解】洛谷P2398 GCD SUM

洛谷P2398 GCD SUM

题意简述

给定 \(n\),求

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

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

  • 对于 \(30\%\) 的数据,\(n\leq 3000\)

  • 对于 \(60\%\) 的数据,\(7000\leq n\leq 7100\)

  • 对于 \(100\%\) 的数据,\(n\leq 10^5\)

思路

暴力(30pts)

复杂度 $ O(n^2) $

\(Code\)

#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) {
	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 = 1; j <= n; j++) {
			ans += __gcd(i, j); //自2021年以后,NOI相关赛事中允许使用下划线函数。
			//需要指出的是,C++17中gcd()被加入标准,可以直接调用。
		}
	}
	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) = k * k\)

  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 此题。

坑点:要开 long long 谁都知道,但快写也要开!!!

\(Code\)

//C++14 (GCC 9) O2, 43ms
//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(long long x, bool is_endl) { //快写不开long long爆70pts
	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 = n; d >= 1; --d) {
		long long k = n / d;
		cnt[d] = k * k;
	}
	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 19:20  WaymingDev  阅读(9)  评论(0)    收藏  举报