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

=== ===

这里放传送门

=== ===

题解

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

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

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

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] 的东西,我们可以考虑再把它搞一下转化成一个带 μ 的式子。以下设 N N M 中的较小者。

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

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

注意到 i j都是 d 的倍数时式子才有意义,所以设i=di j=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 50000
using 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;
}

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

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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值