GCD
原题目:
luogu-P2568
简要描述:
大量利用数论的定理,本质是枚举优化
题目描述
给定正整数
数据范围:
解题思路
1 问题转化
直接枚举x,y 是
的,这里使用构造法,固定一个p,然后求出p对应的(x,y)的数量: 这里显然是O(1)的
题目要求统计满足
对于一个固定的素数
此时,
结论:
对于每一个素数
其实这里的思想就是: 按公约数p,对x,y近性分类,通过函数的逆运算, 求出p对应的集合
,只要找到这个集合,就知道对以的素数p的数量.同样我们也可以知道其他的 对应的数量. 然后对应的数量加起来,就是答案.
问题就变成有多少对
最简单的就是二重循环枚举. 问题变成怎么快速枚举.
这里编程枚举对数(黑白气球),这里假定
2 利用欧拉函数求解互质对
令
利用图形的对称性,我们可以将
- 下三角 (
):满足 的数量。根据欧拉函数的定义,对于固定的 ,满足 且 的 的个数就是 。 - 上三角 (
):由对称性可知,数量与下三角相同。 - 对角线 (
):只有 这一对满足 。
因此,对于上限
这里显然是求:
的前缀和
3 最终公式
我们需要对
最终答案为:
算法实现:线性筛 (Linear Sieve)
由于
- 所有质数。
- 欧拉函数
。
这可以使用**线性筛(欧拉筛)**来实现。
线性筛推导 函数
欧拉函数
- 基础情况:
。 - 当
是素数时: 。 - 当
与素数 互质时 ( ): 利用积性性质: 。 - 当
被素数 整除时 ( ): 此时 已经是 的因子,增加一个 不会增加新的质因子种类: 。
复杂度分析
- 时间复杂度:线性筛预处理为
,统计答案遍历所有素数,复杂度约为 ,总体为 。 - 空间复杂度:需要数组存储素数表、标记数组和
数组,约为 。
代码实现 (C++)
我的代码
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
const ll maxn = 1e7+5;
ll phi[maxn]; // 欧拉函数
bool st[maxn]; // st[i] = 1 表示被删除,不是素数
std::vector<ll> primes; //存素数
ll sum[maxn]; // 欧拉函数的前缀和
ll ans; // 最终答案
void get_phi_line(){
phi[1] = 1;
for(int i = 2;i <= n ;++i ) // i: 2->n
{
if( !st[i])
{
primes.push_back(i);
phi[i] = i - 1;
}
//枚举前面的素数
for(auto p : primes) {
if( i * p > n) break;
st[i * p ] = 1;
if( i % p == 0) {
phi[i * p] = phi[i] * p;
break;
}
else {
phi[p * i] = phi[i] * (p-1);
}
}
}
}
int main (int argc, char *argv[]) {
scanf("%lld",&n);
get_phi_line();
// 求phi前缀和
for(int i = 1;i <= n ;++i ) // i: 1->n
{
sum[i] = sum[i-1] + phi[i];
}
// 枚举素数
for( auto p : primes) {
ans += ( 2 * sum[n/p] - 1);
}
std::cout << ans << "\n";
return 0;
}
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 10000010;
// primes: 存储筛出来的素数
// cnt: 记录素数的个数
// vis: 标记数组,false 表示是素数,true 表示是合数
// phi: 存储欧拉函数值
// sum: 存储欧拉函数的前缀和
int primes[MAXN];
int cnt = 0;
bool vis[MAXN];
int phi[MAXN];
long long sum[MAXN]; // 前缀和可能会溢出 int,使用 long long
void get_phi_and_primes(int n) {
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!vis[i]) {
primes[++cnt] = i;
phi[i] = i - 1; // 素数的欧拉函数性质
}
for (int j = 1; j <= cnt && i * primes[j] <= n; ++j) {
int p = primes[j];
int num = i * p;
vis[num] = true;
if (i % p == 0) {
// p 已经是 i 的最小质因子,这是线性筛的核心
phi[num] = phi[i] * p;
break; // 保证每个合数只被筛一次
} else {
// p 与 i 互质
phi[num] = phi[i] * (p - 1);
}
}
}
// 预处理前缀和
for (int i = 1; i <= n; ++i) {
sum[i] = sum[i - 1] + phi[i];
}
}
int main() {
// 优化 IO
ios::sync_with_stdio(false);
cin.tie(0);
int n;
if (!(cin >> n)) return 0;
get_phi_and_primes(n);
long long ans = 0;
// 枚举每一个素数 p
for (int j = 1; j <= cnt; ++j) {
int p = primes[j];
if (p > n) break;
// 代入推导公式:2 * sum[n/p] - 1
ans += (2 * sum[n / p] - 1);
}
cout << ans << endl;
return 0;
}