[Noi2010]能量采集
题目链接
题目大意
中文题,题目意思很清楚,这里就不说了,总之就是要你求这个:
ans=∑x=1n∑y=1m[2(gcd(x,y)−1)+1]
题解
莫比乌斯反演 化简
可以化简:
ans=2∑x=1n∑y=1mgcd(x,y)−nm
可以看到现在的首要任务是求前面的和式,考虑到gcd(x,y)只有有限个值,我们设
f(d):gcd(x,y)=d的(x,y)的对数
当然x和y都在范围内。
这样的话我们的ans可以改写为:
ans=2∑dd⋅f(d)−nm
看到首要任务是求 f(d) ,这里我们用莫比乌斯反演,于是我们又设:
F(d):d|gcd(x,y)的(x,y)的对数
又因为:
F(d)=nd⋅md
根据反演公式,有:
f(x)=∑x|dμ(dx)⋅nd⋅md
到这里基本又是一些老东西了,令 T=xd 什么的,最后可以化简到:
f(x)=∑TnTmT∑x|Tμ(Tx)
把x换成d,再带入ans,得到:
ans=∑TnTmT∑d|Td⋅μ(Tx)
到这里,我们的 μ(x) 函数有一个性质:
∑d|nd⋅μ(nd)=ϕ(n)
这个可以用用欧拉函数的性质 ∑d|nϕ(d)=n 这个式子莫比乌斯反演得来,所以我们可以把ans变为
ans=∑TnTmT⋅ϕ(T)
可以看到最后的式子非常简单,先筛出 phi(d) 然后分块求和即可。
代码
不能用%I64d,会WA
#include <iostream>
#include <cstring>
#include <cstdio>
#define LL long long
#define maxn 100005
using namespace std;
LL n,m,p[maxn-5],cnt,phi[maxn];
bool vis[maxn-5];
void setup(int high)
{
cnt=0;
memset(p,0,sizeof(p));
memset(vis,0,sizeof(vis));
memset(phi,0,sizeof(phi));
phi[1]=1;
for (int i=2;i<=high;i++)
{
if (!vis[i])
{
vis[i]=1; p[cnt++]=i;
phi[i]=i-1;
}
for (int j=0;j<cnt && i*p[j]<=high;j++)
{
vis[i*p[j]]=1;
if (i%p[j]) phi[i*p[j]]=phi[i]*(p[j]-1);
else
{
phi[i*p[j]]=phi[i]*p[j];
break;
}
}
}
for (int i=1;i<=high;i++) phi[i]+=phi[i-1];
}
int main()
{
scanf("%lld%lld",&n,&m);
setup(min(n,m));
LL last=0,t=min(n,m);
LL ans=0;
for (int i=1;i<=t;i=last+1)
{
last=min(n/(n/i),m/(m/i));
ans+=(LL) (n/i)*(m/i)*(phi[last]-phi[i-1]);
}
ans=(LL)ans*2-n*m;
printf("%lld\n",ans);
return 0;
}