[BZOJ2005][NOI2010]能量采集(莫比乌斯反演)

题目描述

传送门

题解

首先证明对于某个点(x,y),k=gcd(x,y)-1:
设gcd(x,y)=t,令x=at,y=bt,那么在这条直线上的整数点可以表示为(a,b)(2a,2b)(3a,3b)……(x,y),由于不算x,y,则答案为gcd(x,y)-1
那么总损耗2k+1=2×gcd(x,y)-1。
我们最终要求的式子为:
i=1nj=1m(gcd(i,j)21)
=2i=1nj=1mgcd(i,j)nm
那么我们只需要算出 i=1nj=1mgcd(i,j) 这个式子就可以了
推导如下:
i=1nj=1mgcd(i,j)
=i=1nj=1md|gcd(i,j)ϕ(d)
=i=1nj=1md=1n[d|i][d|j]ϕ(d)
=d=1ni=1n[d|i]j=1m[d|j]ϕ(d)
=d=1nndmdϕ(d)

实际上 ndmd 只有 (n+m) 个取值。
可以用分块来求。
需要预处理phi的前缀和。

代码

#include
#include
#include
using namespace std;
#define LL long long

const int max_n=1e5+5;

LL n,m,ans;
LL p[max_n],phi[max_n],prime[max_n];

inline void get_phi(){
    phi[1]=1;
    for (int i=2;i<=n;++i){
        if (!p[i]){
            prime[++prime[0]]=i;
            phi[i]=i-1;
        }
        for (int j=1;j<=prime[0]&&i*prime[j]<=n;++j){
            p[i*prime[j]]=1;
            if (i%prime[j]==0){
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            else
              phi[i*prime[j]]=phi[i]*(prime[j]-1);

        }
        phi[i]+=phi[i-1];
    }
}

int main(){
    scanf("%lld%lld",&n,&m);
    if (n>m) swap(n,m);
    get_phi();
    for (LL i=1,j;i<=n;i=j+1){
        j=min(n/(n/i),m/(m/i));
        ans+=(LL)(phi[j]-phi[i-1])*(n/i)*(m/i);
    }
    printf("%lld\n",ans*2-n*m);
}

总结

首先Orz ATP
求phi的时候不要打phi[prime[j]],尤其是求前缀和的时候。

你可能感兴趣的:(题解,NOI,莫比乌斯反演)