今天的数学课上,Crash小朋友学习了最小公倍数(Least Common Multiple)。对于两个正整数a和b,LCM(a, b)表示能同时被a和b整除的最小正整数。例如,LCM(6, 8) = 24。回到家后,Crash还在想着课上学的东西,为了研究最小公倍数,他画了一张N*M的表格。每个格子里写了一个数字,其中第i行第j列的那个格子里写着数为LCM(i, j)。一个4*5的表格如下:
1 2 3 4 5
2 2 6 4 10
3 6 3 12 15
4 4 12 4 20 看着这个表格,Crash想到了很多可以思考的问题。不过他最想解决的问题却是一个十分简单的问题:这个表格中所有数的和是多少。当N和M很大时,Crash就束手无策了,因此他找到了聪明的你用程序帮他解决这个问题。由于最终结果可能会很大,Crash只想知道表格里所有数的和mod 20101009的值。
输出一个正整数,表示表格中所有数的和mod 20101009的值。
∑∑lcm(i,j) ,i=1..n,j=1..m
=∑∑i*j/gcd(i,j) ,i=1..n,j=1..m
=∑∑∑[gcd(i,j)=d]*i*j/d ,d=1..n,i=1..n,j=1..m (枚举gcd的取值d)
=∑d*(∑∑[gcd(i,j)=1]*i*j) ,d=1..n,i=1..n/d,j=1..m/d
=∑d*(∑a*a*mu[a]*( ∑∑i*j )) ,d=1..n,a=1..n/d,i=1..n/d/a,j=1..m/d/a (由[n=1]=∑mu[d] ,d|n可得)
a的取值只有O(n0.5)个,可以对每个取值只进行一次计算
对每个a,(n/d/a,m/d/a)的取值也是O(n0.5)个,可以用同样方式优化
∑∑i*j用等差数列求和公式可以O(1)计算
#include<cstdio>
typedef long long i64;
const int N=1e7,P=20101009;
int ps[N/5],pp=0,mu[N+5];
i64 m2[N+5];
bool isnp[N+5];
int n,m;
inline i64 f(i64 a,i64 b){
return ((a*(a+1)>>1)%P*((b*(b+1)>>1)%P))%P;
}
inline int min(int a,int b){
return a<b?a:b;
}
int main(){
scanf("%d%d",&n,&m);
if(n>m)n^=m,m^=n,n^=m;
mu[1]=1;
for(int i=2;i<=n;i++){
if(!isnp[i])ps[pp++]=i,mu[i]=-1;
for(int j=0,k;j<pp&&(k=i*ps[j])<=n;j++){
isnp[k]=1;
if(i%ps[j])mu[k]=-mu[i];
else break;
}
}
for(int i=1;i<=n;i++){
m2[i]=(mu[i]*1ll*i*i+m2[i-1])%P;
if(m2[i]<0)m2[i]+=P;
}
i64 ans=0;
for(int d=1,d2;d<=n;d=d2+1){
d2=min(n/(n/d),m/(m/d));
int x=n/d,y=m/d;
i64 s=0;
for(int a=1,b;a<=x;a=b+1){
b=min(x/(x/a),y/(y/a));
s+=(m2[b]-m2[a-1])*f(x/a,y/a)%P;
}
s%=P;
ans=(ans+(1ll*(d+d2)*(d2-d+1)>>1)%P*s%P)%P;
}
if(ans<0)ans+=P;
printf("%lld\n",ans);
return 0;
}