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; }
渺渺时空,茫茫人海,与君相遇,幸甚幸甚