题目大意:给出范围为(0, 0)到(n, n)的整点,你站在原点处,问有多少个整点可见。
线y=x和坐标轴上的点都被(1,0)(0,1)(1,1)挡住了。除这三个钉子外,如果一个点(x,y)不互质,则它就会被点(x0, y0) (x0,y0互质,x/x0==y/y0)挡住。能看见的钉子关于线y=x对称。所以,求出x=2至n的所有与x互质的数的个数φ(x)的和(也就是线y=x右下角(因为φ(x)<x)所有能看见的点的个数)乘以2(对角线两旁的看见的点的个数)+3(那几个特殊点)即为所求。
求φ值时,利用下列性质:
- if n能整除以p,也能整除以p^2,则φ(n)=φ(n/p)*p
- if n能整除以p,但不能整除以p^2,则φ(n)=φ(n/p)*(p-1)。
这样,在线性求2至n的质数个数时将i当作n/p,prime[j]作为p,i*prime[j]作为n,(这样i%prime[j]就相当于n/p/p能否整除)同时更新以后的φ值即可。
#include <cstdio>
#include <cstring>
using namespace std;
const int MAX_N = 1010;
int v[MAX_N], prime[MAX_N], phi[MAX_N];
void Euler(int n)
{
int primeCnt = 0;
memset(v, 0, sizeof(v));
for (int i = 2; i <= n; i++)
{
if (!v[i])
{
prime[primeCnt++] = i;
v[i] = i;
phi[i] = i - 1;
}
for (int j = 0; j < primeCnt && prime[j] <= n / i && prime[j] <= v[i]; j++)
{
v[i * prime[j]] = v[i];
phi[i * prime[j]] = phi[i] * (i%prime[j] ? prime[j] - 1 : prime[j]);
}
}
}
int main()
{
int n, testCase;
scanf("%d", &testCase);
for (int i = 1; i <= testCase; i++)
{
scanf("%d", &n);
Euler(n);
int ans = 0;
for (int j = 2; j <= n; j++)
ans += phi[j];
printf("%d %d %d
", i, n, ans * 2 + 3);
}
return 0;
}
欧拉筛2:
void Euler(int *phi, int n)
{
static int prime[MAX_N];
static bool NotPrime[MAX_N];
int primeCnt=0;
memset(NotPrime,false,sizeof(NotPrime));
phi[1] = 1;
for(int i = 2; i <= n; i++)
{
if(!NotPrime[i])
{
prime[primeCnt++]=i;
phi[i] = i - 1;
}
for(int j=0; j < primeCnt; j++)
{
if(prime[j] * i > n)
break;
NotPrime[prime[j] * i] = true;
if(i % prime[j] == 0)
{
phi[prime[j] * i] = prime[j] * phi[i];
break;
}
else
phi[prime[j] * i] = (prime[j] - 1) * phi[i];
}
}
}