=== ===
这里放传送门
=== ===
题解
题目要求
∑i=1n∑j=1mlcm(i,j)
就直接画柿子就好啦。。
1.由于最小公倍数等于两数乘积除以最大公因数,柿子变为:
∑i=1n∑j=1mi∗jgcd(i,j)
2.枚举两数最大公因数d,显然两个数分别除以它们的最大公因数得到的数应该是互质的:
∑i=1n∑j=1m∑d=1n[d|i][d|j][gcd(id,jd)=1]i∗jd
3.经典的画柿子方法:令 i=i′∗d , j=j′∗d ,代换过去就有:
∑d=1nd∗∑i=1⌊nd⌋∑j=1⌊md⌋[gcd(i,j)=1]i∗j
4.后半段柿子中出现了互质数对之积的和。为了让柿子看起来好看一点就把它拿出来单独算吧。
记为 s(n,m) ,即 ∑i=1n∑j=1m[gcd(i,j)=1]i∗j 。接下来对 s(n,m) 化简。首先枚举约数变成:
∑d=1n∑i=1n∑j=1m[d|i][d|j]μ(d)∗i∗j
再设
i=i′∗d,j=j′∗d
进行替换变成
∑d=1nμ(d)∗d2∑i=1⌊nd⌋∑j=1⌊md⌋i∗j
5.后半段又出现了一个范围内所有数对之积的和,表示为 sum(n,m) 。显然 sum(n,m) 是一个可以在 O(1) 的时间内算出单个值的函数,公式为 sum(n,m)=n∗(n+1)∗m∗(m+1)4 。那么上面的 s(n,m) 就可以表示成 ∑d=1nμ(d)∗d2sum(⌊nd⌋,⌊md⌋) ,利用 ⌊n⌊nd⌋⌋ 的分块做法,我们可以发现 s(n,m) 是一个可以在 O(n√+m−−√) 的时间内算出单个值的函数。
6.求出 s(n,m) 以后第三步中画出的柿子就可以表示成 ∑d=1ns(⌊nd⌋,⌊md⌋) ,也可以用 ⌊n⌊nd⌋⌋ 的分块做法做。两层 O(n√+m−−√) 套起来时间复杂度基本上是 O(n+m) 的。那么线性筛搞到 μ 函数啥的用来算就可以啦
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Mod=20101009;
int n,m,mu[10000010],prm[10000010],sqr[10000010];
bool ext[10000010];
long long sum,ans,h,t1,t2,l,w1,w2,hd,tl;
long long Sum(int n,int m){
long long a1,a2;
a1=(long long)n*(n+1)/2%Mod;
a2=(long long)m*(m+1)/2%Mod;
return a2*a1%Mod; //注意取模
}
long long s(int n,int m){
sum=0;
hd=1;
while (hd<=n){
w1=n/(n/hd);w2=m/(m/hd);
tl=min(w1,w2);
sum+=(long long)(sqr[tl]-sqr[hd-1])%Mod*Sum(n/hd,m/hd)%Mod; //注意强转
hd=tl+1;sum%=Mod; //注意取模
}
return sum%Mod;
}
int main()
{
scanf("%d%d",&n,&m);
if (n>m) swap(n,m);
mu[1]=1;sqr[1]=1;
for (int i=2;i<=n;i++){
if (ext[i]==false){
prm[++prm[0]]=i;
mu[i]=-1;
}
for (int j=1;j<=prm[0];j++){
if (i*prm[j]>n) break;
ext[i*prm[j]]=true;
if (i%prm[j]==0){
mu[i*prm[j]]=0;break;
}else mu[i*prm[j]]=-mu[i];
}
sqr[i]=(long long)i*i*mu[i]%Mod;
sqr[i]=(sqr[i]+sqr[i-1])%Mod; //注意取模
}
ans=0;h=1;
while (h<=n){
t1=n/(n/h);t2=m/(m/h);
l=min(t1,t2);
ans+=(long long)(h+l)*(l-h+1)/2%Mod*s(n/h,m/h)%Mod;//注意强转
h=l+1;ans%=Mod;//注意取模
}
printf("%lld\n",(ans%Mod+Mod)%Mod);
return 0;
}
偏偏在最后出现的补充说明
这题如果全都开成long long会炸内存。。所以要各种强转还要各种取模。。在计算sum函数的时候特别容易炸掉int需要注意。