题目传送门
一、算法分析
设\((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)\)
二、时间复杂度
线性筛法求欧拉函数,所以是\(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;
}