题解 P2158 【[SDOI2008]仪仗队】
提高+的数论第一次过。。。(哭
经过一顿xjb骚推公式,我发现:
假设我们有两个点分别是(x1,y1)和(x2,y2),那么只有当gcd(|x1-x2|,|y1-y2|)=1时,两个点才能互相看见。 那么这道题的答案也就是:gcd(x,y)=1的个数(0<=x<=n-1,0<=y<=n-1)
很容易就会想到欧拉函数。。。
一看数据范围只有4w,于是就没有码线性求欧拉函数我才不会告诉你们其实我并不不会,而是用了欧拉函数的通式:
然后加了个lgn分解质因数,就nlgn的解决了问题。
代码:
#include<cstdio>
#define For(i,x,y) for (int i=x;i<=y;i++)
#define maxn 40010
using namespace std;
int p[maxn],m[maxn];
int n,tot,ans,x,y;
int gcd(int x,int y){
if (y==0) return x;
else return gcd(y,x%y);
}
void fj(int k){
int last=0;
while (m[k]!=k){
if (m[k]!=last) x=x*(m[k]-1),y=y*m[k];
last=m[k];
k=k/m[k];
}
if (m[k]!=last) x=x*(m[k]-1),y=y*m[k];
}//用lgn分解质因数求出
int main(){
scanf("%d",&n);
For(i,2,n){
if (!m[i]) p[++tot]=m[i]=i;
for (int j=1;j<=tot&&i*p[j]<=n;j++){
m[i*p[j]]=p[j];
if (i%p[j]==0) break;
}
}//欧拉筛
For(i,0,n-1){
if (i<=1){
ans++;
continue;
}
x=1,y=1;
fj(i);
int t=gcd(x,y);
x=x/t,y=y/t;
ans+=i/y*x;
}
printf("%d",ans*2-1);//注意最后答案要*2-1,首先gcd(x,y)=1的话gcd(y,x)=1所以就要*2,但是有个特例(1,1)就只能算一次
return 0;
}