0
点赞
收藏
分享

微信扫一扫

AcWing 201. 可见的点

googlefrank 2022-05-23 阅读 47

​​题目传送门​​

一、算法分析

设\((x_0,y_0)\)是某一直线射到的第一个点,则该方程为\(y=kx\),其中\(k=\frac{y_0}{x_0}x\),在该直线上的其他点均是\((x_0,y_0)\)的倍数,即\((mx_0,my_0)\),\((x_0,y_0)\)具有互质的性质,如果不是互质的,并且斜率一样,那么它前面的互质点对,必然把它挡上。

求\((x,y)\)互质的点对数量,就是本题的题意。互质\(gcd(x,y)=1\),联想到欧拉函数。

由于欧拉函数求的是​​1 ~ 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=phi(1)+\sum_{i=2}^{n}phi(i)\)

AcWing 201. 可见的点_c++AcWing 201. 可见的点_#include_02

二、时间复杂度

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

三、暴力实现

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

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;

/**
* 功能:欧拉函数
* @param x
* @return
*/
int phi(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;
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; //叠加两倍的欧拉函数值
cout << i << ' ' << n << ' ' << res << endl;
}

return 0;
}

四、筛法实现

#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; //叠加两倍的欧拉函数值
cout << i << ' ' << n << ' ' << res << endl;
}

return 0;
}

五、利用前缀和优化

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
typedef long long LL;

//筛法求欧拉函数
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;
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> n;
cout << i << ' ' << n << ' ' << s[n] * 2 + 1 << endl;
}
return 0;
}




举报

相关推荐

0 条评论