AcWing 201. 可见的点
. 可见的点
一、题目描述
在一个平面直角坐标系的第一象限内,如果一个点 与原点 的连线中没有通过其他任何点,则称该点在原点处是可见的。
例如,点 就是不可见的,因为它与原点的连线会通过点 。
部分可见点与原点的连线如下图所示:

编写一个程序,计算给定整数 的情况下,满足 的可见点 的数量(可见点不包括原点)。
输入格式
第一行包含整数 ,表示共有 组测试数据。
每组测试数据占一行,包含一个整数 。
输出格式
每组测试数据的输出占据一行。
应包括:测试数据的编号(从 开始),该组测试数据对应的 以及可见点的数量。
同行数据之间用空格隔开。
数据范围
输入样例:
4
2
4
5
231
输出样例:
1 2 5
2 4 13
3 5 21
4 231 32549
二、算法分析
设是某一直线射到的 第一个点,则该方程为,其中,该直线有以下性质:
-
直线方程上的其他点均是的倍数,即,因为斜率相同嘛
-
:
- 此情况 特殊,只有一个点满足,就是
-
时,两者必然互质:
- 证明:如果不是互质的,并且斜率一样,那么它前面的互质点对,必然把它挡上。
求互质的点对数量,就是本题的题意。
,联想到 欧拉函数 。
欧拉函数求的是 中与 互质的数的个数,若需要使用欧拉函数,与互质的数需要小于等于它本身,因此求中,互质的个数需要进行分类,使得某一边小于等于另一边,如图所示,对直线进行切割,使得两边的点对称,只需求右下方区域即可,右下区域的点满足的性质,且 属于到的区间,因此对于每个,求出到中与互质的数的个数,即相当于求的欧拉函数,因此 用筛法求出到的所有欧拉函数。
由于到中的欧拉函数的个数需要算两次(对称),而时只需要算一次,因此

三、时间复杂度
线性筛法求欧拉函数,所以是的。
四、暴力实现
因为本题数据量并不大,也可以不用筛法,直接暴力进行计算每个数字的欧拉函数,也是可以的,但考虑到通用性,还是 建议尽量用筛法求欧拉函数。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
//快读
char buf[1 << 23], *p1 = buf, *p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
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 << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 1010;
// 功能:欧拉函数
int get_euler(int x) {
int res = x;
for (int i = 2; i <= x / i; i++)
if (x % i == 0) {
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 = read();
for (int i = 1; i <= m; i++) {
n = read();
int res = 1; // x=y 记1个
for (int j = 1; j <= n; j++) res += get_euler(j) * 2; //叠加两倍的欧拉函数值
printf("%d %d %d\n", i, n, res);
}
return 0;
}
五、筛法实现
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long LL;
//快读
char buf[1 << 23], *p1 = buf, *p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
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 << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
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 = read();
for (int i = 1; i <= m; i++) {
n = read();
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;
}
六、利用前缀和优化
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long LL;
//快读
char buf[1 << 23], *p1 = buf, *p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
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 << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 1010;
//筛法求欧拉函数
int primes[N], cnt;
bool st[N];
int phi[N];
LL 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);
}
}
}
int main() {
//利用筛法,预处理出欧拉函数值数组
euler(N - 1); //这里注意一下是N-1,防止数组越界
//前缀和优化版本
for (int i = 1; i < N; i++) s[i] = s[i - 1] + phi[i];
int n, m = read();
for (int i = 1; i <= m; i++) {
n = read();
printf("%d %d %d\n", i, n, s[n] * 2 + 1);
}
return 0;
}
七、预处理+欧几里得+二维前缀和
#include <iostream>
using namespace std;
typedef long long LL;
//最大公约数,辗转相除法
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
//快读
char buf[1 << 23], *p1 = buf, *p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
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 << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 2010;
int n, m, a[N][N];
void init() {
for (int i = 1; i <= 1000; i++) //互质,赋值为1
for (int j = 1; j <= 1000; j++)
if (gcd(i, j) == 1) a[i][j] = 1;
for (int i = 1; i <= 1000; i++) //二维前缀和
for (int j = 1; j <= 1000; j++)
a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
}
int main() {
init();
int m = read();
for (int i = 1; i <= m; i++) {
n = read();
printf("%d %d %d\n", i, n, a[n][n] + 2); //加上(1,0),(0,1)两个点
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2018-05-21 python中使用pillow绘制汉字