markdown复制## 1. 题目背景与核心考察点
洛谷P10262"亲朋数"是一道经典的算法竞赛模拟题,主要考察选手对整数性质分析和基础算法优化的掌握能力。题目要求:对于给定的正整数n,定义其"亲朋数"为所有小于n且与n互质的正整数之和。需要高效计算多个查询的亲朋数值。
这道题融合了三个关键知识点:
- 欧拉函数性质(互质数判定)
- 前缀和预处理技巧
- 查询优化策略
在实际竞赛中,这类题目常出现在省选/NOIP中档难度题型中,是区分选手基础算法功底的重要标尺。
## 2. 互质数判定与欧拉函数
### 2.1 互质数定义
两个数互质意味着它们的最大公约数(GCD)为1。计算GCD的经典方法是欧几里得算法:
```cpp
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
欧拉函数φ(n)表示小于n的正整数中与n互质的数的个数。虽然本题要求的是这些数的和而非个数,但欧拉函数的性质仍具有参考价值:
注意:直接套用欧拉函数公式无法得到本题要求的"和",但可以帮助理解互质数的分布规律
最直观的做法是对每个查询n,遍历1到n-1所有数,累加满足gcd(i,n)==1的数:
cpp复制int solve_naive(int n) {
int sum = 0;
for (int i = 1; i < n; ++i) {
if (gcd(i, n) == 1) {
sum += i;
}
}
return sum;
}
对于Q次查询,最大数值N:
发现一个重要规律:对于n>1,与n互质的数总是成对出现(i与n-i互质)。例如n=10:
每对的和为n,因此亲朋数可以表示为:
sum = n * φ(n) / 2
需要单独处理n=1的情况(φ(1)=1,但sum应为0)
cpp复制int solve_math(int n) {
if (n == 1) return 0;
return n * euler_phi(n) / 2;
}
基于欧拉函数公式:
φ(n) = n × ∏(1 - 1/p) (p为n的所有不同质因数)
cpp复制int euler_phi(int n) {
int res = n;
for (int p = 2; p * p <= n; ++p) {
if (n % p == 0) {
res = res / p * (p - 1);
while (n % p == 0) n /= p;
}
}
if (n > 1) res = res / n * (n - 1);
return res;
}
当需要多次查询时,可以使用欧拉筛预处理所有φ值:
cpp复制const int MAX_N = 1e6;
int phi[MAX_N + 1];
void init_phi() {
vector<bool> is_prime(MAX_N + 1, true);
iota(phi, phi + MAX_N + 1, 0);
for (int i = 2; i <= MAX_N; ++i) {
if (is_prime[i]) {
for (int j = i; j <= MAX_N; j += i) {
is_prime[j] = (j == i);
phi[j] = phi[j] / i * (i - 1);
}
}
}
}
cpp复制#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
const int MAX_N = 1e6;
int phi[MAX_N + 1];
void init() {
vector<bool> is_prime(MAX_N + 1, true);
iota(phi, phi + MAX_N + 1, 0);
for (int i = 2; i <= MAX_N; ++i) {
if (is_prime[i]) {
for (int j = i; j <= MAX_N; j += i) {
is_prime[j] = (j == i);
phi[j] = phi[j] / i * (i - 1);
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
init();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
if (n == 1) cout << "0\n";
else cout << 1LL * n * phi[n] / 2 << "\n";
}
return 0;
}
预处理阶段:
查询阶段:
整体复杂度:O(N log log N + Q),可以轻松处理N=1e6,Q=1e5规模的数据
预处理范围选择:
整数溢出处理:
1LL * n * phi[n] / 2确保64位计算输入输出优化:
ios::sync_with_stdio(false)和cin.tie(nullptr)加速IO边界条件测试:
掌握这道题后,可以解决以下变种问题:
竞赛经验:当题目涉及"互质数"相关计算时,优先考虑欧拉函数性质,并观察是否存在配对规律可以简化计算
小数据验证:
对拍测试:
python复制# 对拍验证程序(Python示例)
import math
def brute_force(n):
return sum(i for i in range(1,n) if math.gcd(i,n)==1)
def math_method(n):
if n == 1: return 0
def phi(x):
res = x
for p in range(2,int(x**0.5)+1):
if x % p == 0:
while x % p == 0:
x //= p
res = res // p * (p-1)
if x > 1:
res = res // x * (x-1)
return res
return n * phi(n) // 2
压力测试:
| 方法 | 预处理时间 | 单次查询时间 | 适用场景 |
|---|---|---|---|
| 暴力法 | 无 | O(n log n) | n<1e3的小数据 |
| 数学公式+单次φ计算 | 无 | O(√n) | 查询次数少(n<1e5) |
| 线性筛预处理 | O(n log log n) | O(1) | 查询次数多(n≤1e6) |
实际竞赛中,90%的情况下选择预处理方案最为稳妥
忘记n=1特判:
if(n==1) return 0整数溢出:
1LL*强制类型转换预处理范围不足:
φ计算错误:
if(n>1)判断这道题体现了算法竞赛中三个重要思维模式:
在实际比赛中,遇到数论题时建议:
我个人的经验是:这类题目在纸上推导数学性质的时间往往比编码时间更重要,好的数学分析可以降低100倍的代码复杂度
code复制