传送门
首先证明对于某个点(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=1n∑j=1m(gcd(i,j)∗2−1)
=2∗∑i=1n∑j=1mgcd(i,j)−n∗m
那么我们只需要算出 ∑i=1n∑j=1mgcd(i,j) 这个式子就可以了
推导如下:
∑i=1n∑j=1mgcd(i,j)
=∑i=1n∑j=1m∑d|gcd(i,j)ϕ(d)
=∑i=1n∑j=1m∑d=1n[d|i][d|j]ϕ(d)
=∑d=1n∑i=1n[d|i]∑j=1m[d|j]ϕ(d)
=∑d=1n⌊nd⌋⌊md⌋ϕ(d)
实际上 ⌊nd⌋⌊md⌋ 只有 (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]],尤其是求前缀和的时候。