题意:
求i=1∑nj=1∑nlcm(i,j) mod 109+7n<=1010
题目分析:
化简,i=1∑nj=1∑nlcm(i,j)=i=1∑nj=1∑ngcd(i,j)i∗j
枚举gcd=d=1∑nd∗i=1∑dnj=1∑dn[gcd(i,j)==1]∗i∗j
OK,且慢,这里的gcd(i,j)==1就很讲究了,有两种方法
法一:欧拉函数 φ
因为∑i=1dn∑j=1dn是对称的,所以可以化为
=d=1∑nd∗⎩⎨⎧⎝⎛2i=1∑dnij=1∑ij[(i,j)==1]⎠⎞−1⎭⎬⎫=d=1∑nd∗⎩⎨⎧⎝⎛2i=1∑dni∗2i∗φ(i)+[i=1]⎠⎞−1⎭⎬⎫=d=1∑nd∗i=1∑dni2∗φ(i)
然后按照d分块优化,要求i2∗φ(i)的前缀和,把它看做id⋅id⋅φ
那么(id⋅id⋅φ)∗(id⋅id)=
i∣n∑i2∗φ(i)∗(in)2=n2i∣n∑φ(i)=n3=id3
id2和id3的前缀和都很好求,所以问题就解决了
i=1∑ni3=i=1∑nd∣i∑d2∗φ(d)∗(di)2=i=1∑ni2d=1∑ind2∗φ(d)=i=1∑ni2∗F(in)
把i=1提出来,那么F(n)=i=1∑ni3−i=2∑ni2∗F(in)
i2∗φ(i)是积性函数,可以线性筛一部分,然后分块优化递归求解即可
法一的核心点:i=1∑ni∗[(n,i)==1]=2n∗φ(n)+[n=1]
因为倘若i与n互质,则n-i也与n互质,这样的对数有φ(n)/2对,每对的和都为n
法二:莫比乌斯反演 μ
d=1∑nd∗i=1∑dnj=1∑dn[gcd(i,j)==1]∗i∗j=d=1∑nd∗i=1∑dnj=1∑dni∗j∗k∣(i,j)∑μ(k)=d=1∑nd∗k=1∑dnμ(k)k2i=1∑dknj=1∑dkni∗j
令dk=T
=T=1∑n(2Tn∗(Tn+1))2∗k∣T∑μ(k)∗k2∗kT
按照T分块优化,要求∑k∣Tμ(k)∗k2∗kT的前缀和(这时候最好不要把T提出去),把它看做(id⋅id⋅μ)∗id
那么(id⋅id⋅μ)∗id∗(id⋅id)=
(id⋅id⋅μ)∗(id⋅id)∗id=⎝⎛d∣T∑d2∗μ(d)∗(dT)2⎠⎞∗id=⎝⎛T2d∣T∑μ(d)⎠⎞∗id=T∣N∑T2∗(TN)d∣T∑μ(d)=NT∣N∑T[T==1]=id
id2和id的前缀和都很好求,剩下的部分就和法一的转化方法类似了
法二的核心:[n==1]=d∣n∑μ(d)
两种方法的共同点:
都利用了卷积(递归方法)来求解n比较大时,通常n<=1e9,1e10,1e11,的前缀和问题,构造出f∗g=h的形式,当g,h的前缀和可以O(1)求解,且f为积性函数,可以线性筛一部分时,求解f的前缀和的时间复杂度为O(n32)
Ps:两种方法都试图通过卷积消去φ或μ前面的系数,从而简化形式,这是个初学时很难想到的点(至少我没想到。。。)。
此处贴出法一的代码,法二请读者自行尝试。。。
#include<cstdio>
#include<map>
#define LL long long
using namespace std;
const int mod = 1e9+7, N = 5000000, inv2 = 5e8+4, inv6 = 166666668;
int p[N/10],sum[N+5];
bool v[N+5];
map<LL,int>F;
inline int sqr(int x){return 1ll*x*x%mod;}
void Prime()
{
sum[1]=1;int cnt=0;
for(int i=2;i<=N;i++)
{
if(!v[i]) p[++cnt]=i,sum[i]=1ll*i*i%mod*(i-1)%mod;
for(int j=1,k;j<=cnt&&p[j]*i<=N;j++)
{
v[k=p[j]*i]=1;
if(i%p[j]==0) {sum[k]=1ll*sum[i]*p[j]%mod*sqr(p[j])%mod;break;}
sum[k]=1ll*sum[i]*sum[p[j]]%mod;
}
}
for(int i=2;i<=N;i++) sum[i]=(sum[i]+sum[i-1])%mod;
}
inline LL calc(LL n){return n*(n+1)%mod*(2*n+1)%mod*inv6%mod;}
int solve(LL n)
{
if(n<=N) return sum[n];
if(F.count(n)) return F[n];
int ret=sqr(n%mod*((n+1)%mod)%mod*inv2%mod);
for(LL i=2,j;i<=n;i=j+1)
{
j=n/(n/i);
ret=(ret-(calc(j%mod)-calc((i-1)%mod))*solve(n/i))%mod;
}
return F[n]=ret;
}
int main()
{
Prime();
LL n,ans=0;
scanf("%lld",&n);
for(LL i=1,j;i<=n;i=j+1)
{
j=n/(n/i);
ans=(ans+(i+j)%mod*(j-i+1)%mod*inv2%mod*solve(n/i))%mod;
}
printf("%lld",(ans+mod)%mod);
}