AcWing 201. 可见的点

\(AcWing\) \(201\). 可见的点

一、题目描述

在一个平面直角坐标系的第一象限内,如果一个点 \((x,y)\) 与原点 \((0,0)\) 的连线中没有通过其他任何点,则称该点在原点处是可见的。

例如,点 \((4,2)\) 就是不可见的,因为它与原点的连线会通过点 \((2,1)\)

部分可见点与原点的连线如下图所示:

编写一个程序,计算给定整数 \(N\) 的情况下,满足 \(0≤x,y≤N\) 的可见点 \((x,y)\) 的数量(可见点不包括原点)。

输入格式
第一行包含整数 \(C\),表示共有 \(C\) 组测试数据。
每组测试数据占一行,包含一个整数 \(N\)

输出格式
每组测试数据的输出占据一行。

应包括:测试数据的编号(从 \(1\) 开始),该组测试数据对应的 \(N\) 以及可见点的数量。

同行数据之间用空格隔开。

数据范围
\(1≤N,C≤1000\)

输入样例

4
2
4
5
231

输出样例:

1 2 5
2 4 13
3 5 21
4 231 32549

二、算法分析

\((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\)的所有欧拉函数

由于\(2\)\(n\)中的欧拉函数的个数需要算两次(对称),而\(x=y=1\)时只需要算一次,因此\(\displaystyle res=1 +\sum_{i=2}^{n}phi(i)\)

三、时间复杂度

线性筛法求欧拉函数,所以是\(O(n)\)的。

四、暴力实现

因为本题数据量并不大,也可以不用筛法,直接暴力\(O(n^2)\)进行计算每个数字的欧拉函数,也是可以的,但考虑到通用性,还是 建议尽量用筛法求欧拉函数

#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;
}
posted @ 2022-05-21 11:06  糖豆爸爸  阅读(64)  评论(0编辑  收藏  举报
Live2D