BZOJ2005: [Noi2010]能量采集

【传送门:BZOJ2005


简要题意:

  给出n*m个格子,位置从(1,1)到(n,m)

  在(0,0)的位置上有一个机器,如果(x,y)这个点与(0,0)的连线上覆盖了k个点(不包括(0,0)和(x,y)),则这个点的代价为2*k+1

  求出所有n*m个点的代价和


题解:

  莫比乌斯反演

  显然代价和等于$\sum_{x=1}^{n}\sum_{y=1}^{m}2*gcd(x,y)-1$,可以转化为$-n*m+2*\sum_{x=1}^{n}\sum_{y=1}^{m}gcd(x,y)$

  那么我们只要求出$\sum_{x=1}^{n}\sum_{y=1}^{m}gcd(x,y)$就可以了

  因为一个数的所有因子的欧拉函数之和等于这个数

  所以转化为$$\sum_{x=1}^{n}\sum_{y=1}^{m}\sum_{d=1}^{n}[d|x且d|y]\phi(d)$$

  交换和式得到$$\sum_{d=1}^{n}\sum_{x=1}^{n}[d|x]\sum_{y=1}^{m}[d|y]\phi(d)$$

  实际上就等于$\sum_{d=1}^{n}\frac{n}{d}*\frac{m}{d}*\phi(d)$

  预处理欧拉函数就可以了


参考代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
int prime[110000],v[110000];
LL phi[110000];
void pre(int n)
{
    memset(v,0,sizeof(v));
    int m=0;phi[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(v[i]==0)
        {
            v[i]=i;
            prime[++m]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=m;j++)
        {
            if(prime[j]>n/i||prime[j]>v[i]) break;
            v[prime[j]*i]=prime[j];
            if(i%prime[j]==0) phi[i*prime[j]]=phi[i]*prime[j];
            else phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
}
int main()
{
    pre(100000);
    int n,m;
    scanf("%d%d",&n,&m);
    LL ans=0;
    for(int i=1;i<=min(n,m);i++) ans+=(LL)(n/i)*(m/i)*phi[i];
    printf("%lld\n",2*ans-(LL)n*m);
    return 0;
}

 

posted @ 2018-10-24 10:21  Star_Feel  阅读(168)  评论(0编辑  收藏  举报