[BZOJ2154]Crash的数字表格(数论)

来源:互联网 发布:mac限免软件 编辑:程序博客网 时间:2024/05/16 16:15

=== ===

这里放传送门

=== ===

题解

题目要求

i=1nj=1mlcm(i,j)

就直接画柿子就好啦。。
1.由于最小公倍数等于两数乘积除以最大公因数,柿子变为:
i=1nj=1mijgcd(i,j)

2.枚举两数最大公因数d,显然两个数分别除以它们的最大公因数得到的数应该是互质的:
i=1nj=1md=1n[d|i][d|j][gcd(id,jd)=1]ijd

3.经典的画柿子方法:令i=idj=jd,代换过去就有:
d=1ndi=1ndj=1md[gcd(i,j)=1]ij

4.后半段柿子中出现了互质数对之积的和。为了让柿子看起来好看一点就把它拿出来单独算吧。
记为s(n,m),即i=1nj=1m[gcd(i,j)=1]ij。接下来对s(n,m)化简。首先枚举约数变成:
d=1ni=1nj=1m[d|i][d|j]μ(d)ij
再设i=id,j=jd进行替换变成
d=1nμ(d)d2i=1ndj=1mdij

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),利用nnd的分块做法,我们可以发现s(n,m)是一个可以在O(n+m)的时间内算出单个值的函数。
6.求出s(n,m)以后第三步中画出的柿子就可以表示成d=1ns(nd,md),也可以用nnd的分块做法做。两层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需要注意。

0 0