208 lines
6.4 KiB
Markdown
208 lines
6.4 KiB
Markdown
##[$AcWing$ $201$. 可见的点](https://www.acwing.com/problem/content/203/)
|
||
|
||
### 一、题目描述
|
||
在一个平面直角坐标系的第一象限内,如果一个点 $(x,y)$ 与原点 $(0,0)$ 的连线中没有通过其他任何点,则称 **该点在原点处是可见的** 。
|
||
|
||
例如,点 $(4,2)$ 就是不可见的,因为它与原点的连线会通过点 $(2,1)$。
|
||
|
||
部分可见点与原点的连线如下图所示:
|
||
<center><img src='https://www.acwing.com/media/article/image/2019/01/18/19_a68c1a281a-3090_1.png'></center>
|
||
|
||
编写一个程序,计算给定整数 $N$ 的情况下,满足 $0≤x,y≤N$ 的可见点 $(x,y)$ 的数量(可见点不包括原点)。
|
||
|
||
**输入格式**
|
||
第一行包含整数 $C$,表示共有 $C$ 组测试数据。
|
||
每组测试数据占一行,包含一个整数 $N$。
|
||
|
||
**输出格式**
|
||
每组测试数据的输出占据一行。
|
||
|
||
应包括:测试数据的编号(从 $1$ 开始),该组测试数据对应的 $N$ 以及可见点的数量。
|
||
|
||
同行数据之间用空格隔开。
|
||
|
||
**数据范围**
|
||
$1≤N,C≤1000$
|
||
|
||
**输入样例**:
|
||
```cpp {.line-numbers}
|
||
4
|
||
2
|
||
4
|
||
5
|
||
231
|
||
```
|
||
**输出样例:**
|
||
```cpp {.line-numbers}
|
||
1 2 5
|
||
2 4 13
|
||
3 5 21
|
||
4 231 32549
|
||
```
|
||
|
||
### 二、前导知识
|
||
|
||
**[欧拉函数专题](https://www.cnblogs.com/littlehb/p/15213612.html)**
|
||
|
||
### 三、算法分析
|
||
|
||
设$(x_0,y_0)$是某一直线射到的 **第一个点**,则该方程为$y=kx$,其中$k=\frac{y_0}{x_0}$,该直线有以下性质:
|
||
|
||
|
||
- ① 直线方程上的其他点均是$(x_0,y_0)$的倍数,即$(m\cdot x_0,m\cdot y_0)$,因为斜率相同嘛
|
||
|
||
|
||
- ② $\frac{x_0}{y_0}=1$:
|
||
- 此情况 **特殊**,只有一个点满足,就是$(1,1)$
|
||
|
||
|
||
- ③ $\frac{x_0}{y_0} \neq 1$时,两者必然互质:
|
||
- **证明**:如果不互质,并且斜率一样,那么它前面的互质点对,必然把它挡上。
|
||
|
||
|
||
求$(x,y)$互质的点对数量,就是本题的题意。
|
||
|
||
$gcd(x,y)=1$,联想到 **欧拉函数** 。
|
||
|
||
**欧拉函数求的是$1 \sim n$ 中与 $n$ 互质的数的个数**,若需要使用欧拉函数,与$n$互质的数需要小于等于它本身,因此求$0 <= x ,y <= n$中,$(x,y)$互质的个数需要进行分类,使得某一边小于等于另一边,如图所示,对$y = x$直线进行切割,使得两边的点对称,只需求右下方区域即可,右下区域的点满足$x > y$的性质,且$y$ 属于$1$到$x - 1$的区间,因此对于每个$x$,求出$1$到$x - 1$中与$x$互质的数的个数,即相当于求$x$的欧拉函数,因此 **用筛法求出$1$到$x$的所有欧拉函数**。
|
||
> <font color='red' size=4><b>注:
|
||
> ① 这里说的$x>y$是指离散的整数点
|
||
> ② 最大是$n$,同时也要考虑每个小于$n$的数字的欧拉函数值
|
||
> 即 $sum=2*(phi(2)+phi(3)+...+phi(n))+1$
|
||
> </b></font>
|
||
|
||
由于$2$到$n$中的欧拉函数的个数需要算两次(对称),而$x=y=1$时只需要算一次,因此$\displaystyle res=1 +2*\sum_{i=2}^{n}phi(i)$
|
||
|
||
<center><img src='https://cdn.acwing.com/media/article/image/2020/04/25/7416_f55f92b286-2020-04-25_215904.jpg'></center>
|
||
|
||
#### 时间复杂度
|
||
线性筛法求欧拉函数,所以是$O(n)$的。
|
||
|
||
|
||
### 四、暴力实现
|
||
因为本题数据量并不大,也可以不用筛法,直接暴力$O(n^2)$进行计算每个数字的欧拉函数,也是可以的,但考虑到通用性,还是 <font color='red' size=4><b>建议用筛法求欧拉函数</b></font>。
|
||
|
||
```cpp {.line-numbers}
|
||
#include <bits/stdc++.h>
|
||
using namespace std;
|
||
const int N = 1010;
|
||
|
||
// 欧拉函数
|
||
int phi(int x) {
|
||
int res = x;
|
||
for (int i = 2; i * i <= x; i++) // 从2枚举到sqrt(x)
|
||
if (x % i == 0) { // 如果i是x的小因子
|
||
res = res / i * (i - 1);
|
||
while (x % i == 0) x /= i; //
|
||
}
|
||
if (x > 1) res = res / x * (x - 1);
|
||
return res;
|
||
}
|
||
|
||
int main() {
|
||
int n, m;
|
||
cin >> m;
|
||
for (int i = 1; i <= m; i++) {
|
||
cin >> n;
|
||
int res = 1; // x=y 记1个
|
||
for (int j = 1; j <= n; j++) res += phi(j) * 2; // 叠加两倍的欧拉函数值
|
||
printf("%d %d %d\n", i, n, res);
|
||
}
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
### 五、筛法实现
|
||
```cpp {.line-numbers}
|
||
#include <bits/stdc++.h>
|
||
using namespace std;
|
||
const int N = 1010;
|
||
|
||
// 筛法求欧拉函数
|
||
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[i * primes[j]] = true;
|
||
if (i % primes[j] == 0) {
|
||
phi[i * primes[j]] = phi[i] * primes[j];
|
||
break;
|
||
}
|
||
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
int main() {
|
||
// 利用筛法,预处理出欧拉函数值数组
|
||
euler(N - 1); // 这里注意一下是N-1,防止数组越界
|
||
|
||
int n, m;
|
||
cin >> m;
|
||
for (int i = 1; i <= m; i++) {
|
||
cin >> n;
|
||
int res = 1; // x=y 记1个
|
||
for (int j = 1; j <= n; j++) res += phi[j] * 2; // 叠加两倍的欧拉函数值
|
||
printf("%d %d %d\n", i, n, res);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
前缀和优化一下:
|
||
|
||
```cpp {.line-numbers}
|
||
#include <bits/stdc++.h>
|
||
using namespace std;
|
||
#define int long long
|
||
#define endl "\n"
|
||
const int N = 1010;
|
||
|
||
// 筛法求欧拉函数
|
||
int primes[N], cnt;
|
||
bool st[N];
|
||
int phi[N];
|
||
int s[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[i * primes[j]] = 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() {
|
||
// 利用筛法,预处理出欧拉函数值数组
|
||
euler(N - 1); // 这里注意一下是N-1,防止数组越界
|
||
|
||
// 前缀和优化版本
|
||
for (int i = 1; i < N; i++) s[i] = s[i - 1] + phi[i];
|
||
|
||
int n, m;
|
||
cin >> m;
|
||
for (int i = 1; i <= m; i++) {
|
||
cin >> n;
|
||
printf("%d %d %d\n", i, n, s[n] * 2 + 1);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
``` |