[BZOJ3994][SDOI2015]约数个数和(数论)

来源:互联网 发布:第五代软件下载 编辑:程序博客网 时间:2024/05/21 09:07

=== ===

这里放传送门

=== ===

题解

SDOI怎么净出这样goushi的数论。。让不让人活T_T

题目要求的是i=1Nj=1Md(i×j)。啥?约数个数是可以筛的?但是这个题一看数据范围显然不可行啊QwQ。。但是这个题目关键的一个特点就是它要求d的那个东西已经给表示成了i×j,那么如果我们知道i的所有约数pij的所有约数qi,那么任意一个pi×qj一定能表示i×j的一个约数,同样i×j的一个约数也必定能表示成pi×qj的形式。

那么我们只需要知道有多少本质不同的pi×qj就可以了。显然如果piqj不互质,那么这一组乘积一定能被另外两个互质的数的乘积代替,并且这两个数肯定也分别是ij的约数。那么我们就有了这么一个公式:

d(i×j)=p|iq|j[(p,q)==1]

于是我们把这个式子代入上面那个初始公式得到:

i=1Nj=1Mp|iq|j[(p,q)==1]

然而只有这个式子还是不够,我们需要进一步化简。首先把式子展开然后胡乱地移动一下,变成
p=1Ni=1N[p|i]q=1Mj=1M[q|j][(p,q)==1]

可以发现其中i=1N[p|i]这一句就是在问1..N的数字中有多少数字是p的倍数,那么这整个式子和Np是相等的。同样,j=1M[q|j]这个东西可以换成Mq。再整理一下式子就变成:

i=1Nj=1MNiMj[(i,j)==1]

发现后面有一个[(i,j)==1]的东西,我们可以考虑再把它搞一下转化成一个带μ的式子。以下设NNM中的较小者。

首先利用反演公式e=μ×1,可以把式子转化成

i=1Nj=1MNiMjd=1N[d|i][d|j]μ(d)

注意到ij都是d的倍数时式子才有意义,所以设i=dij=dj,然后代进去替换可以得到:

d=1Nμ(d)i=1Ndj=1MdNi×dMj×d

接下来我们要利用下取整函数的一个性质:xa×b=xab。于是对于Ni×d这个东西,我们可以把它化成Ndi。这个时候再观察i=1NdNdi这个东西,可以发现它是一个关于Nd的函数。最后式子变成:

d=1Nμ(d)f(Nd)f(Md)

对于f(n)=i=1nni,我们可以用同样的分块做法在O(n)的时间内求得单个f值,那么就可以用O(nn)的时间预处理所有需要的f值。那么最终计算的时候求f的复杂度变成了O(1),那么上面这个式子显然可以用分块的做法在O(n+m)的时间复杂度内求解。最后的时间复杂度是O(nn+T(n+m))

代码

#include<cstdio>#include<cstring>#include<algorithm>#define N 50000using namespace std;int mu[50010],prm[50010],f[50010],T,n,m,p1,p2;bool ext[50010];long long ans;void calc_mu(){    mu[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];        }        mu[i]+=mu[i-1];    }}int calc_f(int n){    int ans=0;    for (int i=1,j;i<=n;i=j+1){        j=n/(n/i);        if (j>n) j=n;        ans+=(n/i)*(j-i+1);    }    return ans;}int main(){    calc_mu();    for (int i=1;i<=N;i++)      f[i]=calc_f(i);    scanf("%d",&T);    for (int time=1;time<=T;time++){        scanf("%d%d",&n,&m);        ans=0;p1=p2=1;        if (n>m) swap(n,m);        for (int i=1;;){            if (p1<=p2){                ans+=(long long)f[n/i]*f[m/i]*(mu[p1]-mu[i-1]);                i=p1+1;            }else{                ans+=(long long)f[n/i]*f[m/i]*(mu[p2]-mu[i-1]);                i=p2+1;            }            if (i>n) break;            p1=n/(n/i);p2=m/(m/i);        }        printf("%I64d\n",ans);    }    return 0;}

偏偏在最后出现的补充说明

常用的经典的化式子方法一定要记熟,做题的时候能一下子想到才行。

其实现在看这个题还是挺简单的一个数论。。用的都是基础公式,化式子的方法也都非常经典。。但是当时不知道为啥瞪着题解看了一天愣是没看懂。。

0 0
原创粉丝点击