Description
今天的数学课上,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的值。
Input
输入的第一行包含两个正整数,分别表示N和M。
Output
输出一个正整数,表示表格中所有数的和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; }