148 lines
5.1 KiB
Markdown
148 lines
5.1 KiB
Markdown
##[$AcWing$ $220$. 最大公约数](https://www.acwing.com/problem/content/222/)
|
||
|
||
### 一、题目描述
|
||
给定整数 $N$,求 $1≤x,y≤N$ 且 $GCD(x,y)$ **为素数**的 数对$(x,y)$ 有多少对。
|
||
|
||
$GCD(x,y)$ 即求 $x,y$ 的最大公约数。
|
||
|
||
**输入格式**
|
||
输入一个整数 $N$。
|
||
|
||
**输出格式**
|
||
输出一个整数,表示满足条件的数对数量。
|
||
|
||
**数据范围**
|
||
$1≤N≤10^7$
|
||
|
||
**输入样例**:
|
||
```cpp {.line-numbers}
|
||
4
|
||
```
|
||
**输出样例**:
|
||
```cpp {.line-numbers}
|
||
4
|
||
```
|
||
|
||
|
||
### 二、解题思路
|
||
|
||
最开始读错题,成了: $1<=x,y<=N$,并且$gcd(x,y)=1$有多少数对?
|
||
|
||
这不就是在计算$\displaystyle \sum_{i=1}^{N}φ(i)$吗?
|
||
|
||
其实本题不是说$gcd(x,y)=1$,而是说$gcd(x,y)=p$,<font color='red'><b>其中$p$是质数</b></font> 。
|
||
|
||
|
||
#### 1、解读样例
|
||
先来看一下样例:$n=4$时,答案是$4$。
|
||
|
||
手动枚举一下:
|
||
|
||
$gcd(2,2)=2$,$gcd(3,3)=3$,$gcd(2,4)=2$,$gcd(4,2)=2$
|
||
|
||
就这四个,因为只有这几种组合,最大公约数才是质数,组数是$4$组。
|
||
|
||
> $gcd(2,3)=1,gcd(3,2)=1,gcd(3,4)=1,gcd(4,3)=1$,结果不是质数而是$1$,不符合要求。
|
||
|
||
**可以看得出来**,由这个样例推出:这里$gcd(2,4)=gcd(4,2)=2$被看成了两组,也说是说 **对于不同的$x,y$,视为两组结果**。
|
||
|
||
> <font color='red' size=4><b>小结:手动计算一下样例非常重要,可以帮我们更深刻理解题意!</b></font>
|
||
|
||
|
||
#### 2、推式子
|
||
这是一个**最大公约数的典型转化技巧**:把$p$除过来,形成:
|
||
|
||
$$\large gcd(\frac{x}{p},\frac{y}{p})=1$$
|
||
|
||
<center><img src='https://cdn.acwing.com/media/article/image/2020/11/23/39715_d94e747e2d-220.PNG'></center>
|
||
|
||
为什么要除过来呢?因为$p$的取值是多种多样的,我们不知道$p$具体是什么,如果我们把$p$除过来,是不是就会简化很多呢?其实就是从骨子里想要用欧拉函数解决问题,不搞成和欧拉函数差不多的样子,怎么能用得上人家呢? **除吧,技巧,背吧,技巧~**
|
||
|
||
这是在 **最大公约数中最常见的变化技巧** 之一,后面还有比较难的莫比乌斯变换,以后再讲~
|
||
|
||
现在,可以视$\large x'=\frac{x}{p},y'=\frac{y}{p}$,即$gcd(x',y')=1$,这不就用上欧拉函数了吗?只不过数据范围有了变化,变成了$1<=x',y'<=\frac{N}{p}$
|
||
|
||
到了这里,$p$是啥呢?它是需要在$1\sim n$之间枚举的每一个质数,用线性筛法求得,然后遍历就可以计算了。
|
||
```cpp {.line-numbers}
|
||
for (int i = 0; i < cnt; i++) {
|
||
int p = primes[i]; //枚举1~n中的所有质数p
|
||
......
|
||
}
|
||
```
|
||
|
||
如果$p$被枚举到,比如现在$p=2$,那我们如何来求$gcd(x',y')=1$的数对个数呢?
|
||
|
||
**这就是简单的欧拉函数+求和(可以使用前缀和优化)啊~**
|
||
|
||
数据范围:$1<=x',y'<=\frac{N}{p}$
|
||
|
||
- 像[$AcWing$ $201$ 可见的点](https://www.acwing.com/problem/content/203/)一样,计算出前缀和,可以避免每次重复计算:
|
||
```c++
|
||
//维护前缀和
|
||
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + phi[i];
|
||
```
|
||
|
||
- 这里有一个第一象限$45^。$直线上点的问题,就是$x'=y'$时,分别在下方和上方计算了,需要再减去$1$,比如$(2,2),(2,2)$这是不行的,算重复了,减去$1$个。
|
||
|
||
```c++
|
||
res += s[n / p] * 2 - 1; //推导的公式
|
||
```
|
||
|
||
$$\sum_{i=1}^N (2 * \sum_{i=1}^{\frac{N}{p}} φ(i)-1 ) $$
|
||
|
||
### 三、实现代码
|
||
|
||
```cpp {.line-numbers}
|
||
#include <bits/stdc++.h>
|
||
using namespace std;
|
||
|
||
#define int long long
|
||
#define endl "\n"
|
||
const int N = 1e7 + 10; // 这家伙好大啊
|
||
int s[N];
|
||
|
||
// 筛法求欧拉函数值
|
||
int primes[N], cnt;
|
||
bool st[N];
|
||
int phi[N];
|
||
void euler(int n) {
|
||
phi[1] = 1;
|
||
for (int i = 2; i <= n; i++) {
|
||
if (!st[i]) {
|
||
primes[cnt++] = i;
|
||
phi[i] = i - 1;
|
||
}
|
||
for (int j = 0; primes[j] * i <= n; j++) {
|
||
st[primes[j] * i] = true;
|
||
if (i % primes[j] == 0) {
|
||
phi[i * primes[j]] = phi[i] * primes[j];
|
||
break;
|
||
}
|
||
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
signed main() {
|
||
int n;
|
||
cin >> n;
|
||
|
||
// 欧拉函数筛法
|
||
euler(n);
|
||
|
||
// 前缀和
|
||
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + phi[i];
|
||
|
||
int res = 0;
|
||
for (int i = 0; i < cnt; i++)
|
||
res += s[n / primes[i]] * 2 - 1; // 推导的公式
|
||
|
||
cout << res << endl;
|
||
}
|
||
```
|
||
|
||
### 四、答疑解惑
|
||
|
||
#### $Q$:为什么$AcWing$ $201$中可以使用$euler(N-1)$,而本题采用的是$euler(n)$呢?我换成$euler(N-1)$发现结果不对啊?
|
||
|
||
答:因为本题除了要获取一个$phi$的静态结果数组外,还要用到欧拉函数筛法中的副产品$primes$数组,$cnt$变量。以此来枚举区间内所有的合法质数,对于每个质数范围内,进行求欧拉函数值,整体求和。如果我们也采用的$N-1$做为参数,那么这个$cnt$就被放大了,结果就会有问题,当然,也可以采用判断的办法进行弥补,但那样就有点啰嗦了,性能也下降为$3639 ms$ |