BZOJ2005 NOI2010 能量采集 欧拉函数

题意:求$\sum\limits_{i = 1}^N {\sum\limits_{j = 1}^M {f(i,j)} } $,其中f(i,j)=(0,0)与(i,j)连线上点的数量

题解:

如果一个点(x',y')在(0,0)与(x,y)的连线上,则有gcd(x',y')==gcd(x,y)。因此f(i,j)=(gcd(i',j')=gcd(i,j))且i'<i,j'<j的点的数量。

由于题目中不需要统计(x,y)本身,所以计算出的2*ans=ANS+N*M=>ANS=2*ans-N*M。
现在我们来看如何求ans,我们枚举倍数i,则满足x%i==0 && y%i==0的点(x,y)的数量就有(N/i)*(M/i)个,而对于每个这样的点,其与(0,0)连线上的点就有phi(i)个,因此
ans=sum(phi(i)*(N/i)*(M/i))
然后用分块就可以将复杂度降为sqrt(N)

#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long

const int MAXN=100000+2;
ll N,M,ans;
ll f[MAXN];
ll phi[MAXN],prime[MAXN];
bool flag[MAXN];

int main(){
    cin >> N >> M;
    if(M>N) swap(N,M);
    
    phi[1]=1;
    for(int i=2,k=0;i<=n;i++){
        if(!flag[i]) prime[++k]=i,phi[i]=i-1;
        for(int j=1;j<=k && prime[j]*i<=n;j++){
            flag[i*prime[j]]=1;
            if(!(i%prime[j])){
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
    
    for(int i=2;i<=M;i++) phi[i]+=phi[i-1];
    
    for(int i=1,j;i<=M;i=j+1){
        j=min(N/(N/i),M/(M/i));
        ans+=(phi[j]-phi[i-1])*(N/i)*(M/i);
    }
    
    cout << 2*ans-N*M << endl;

    return 0;
}
View Code

 

posted @ 2017-02-26 15:13  WDZRMPCBIT  阅读(...)  评论(...编辑  收藏