bzoj3994[SDOI2015]约数个数和

题目链接:bzoj3994
题目大意:
设d(x)为x的约数个数,给定N、M,求 ni=1mj=1d(ij)

题解:
莫比乌斯反演

知识储备

公式: d(ij)=x|iy|j[gcd(x,y)=1]
证明转自http://blog.csdn.net/clover_hxy/article/details/51282493 修改了一下变量名

证明如下:我们知道了这个自变量的乘积拆分形式,那么如果我们枚举 i 的所有约数x,枚举 j 的所有约数y,那么 xy 一定是 ij 的一个约数,于是所有的 xy 就包括了 ij 的所有约数,那么我们只要知道有多少不同的 xy 就可以了。为了知道不同的 xy ,我们可以考虑如何去掉重复的 xy 。这里我们考虑把 x y分为互质和不互质两种情况,设 gcd(x,y)=k ,于是 x=pk y=qk 。那么 xy 就可以重新组合成两个互质数的积 pkk q ,而这两个数也分别是i j 的约数,即合法的x y 。这一点说明了一个问题:如果枚举到的x y 不互质,那么xy这个数一定可以由另外一对互质的 x y 表示,也就是这一对 x y是重复的;而当 x y互质的时候, pkk q 显然就是x y ,所以它是唯一的。于是我们总结一下上面的结论:所有x y 的积包括了ij的所有约数,当 x y不互质时, xy 可以由另一对互质的 x y 表示。所以我们得到所有互质的 x y的乘积就不重复地包含了 ij 的所有约数。


所以要求的式子即为:

i=1nj=1md(ij)=i=1nj=1mx|iy|j[gcd(x,y)=1]

化简
i=1nj=1md(ij)=x=1ny=1mnx×my×[gcd(x,y)=1]

反演一下
i=1nj=1md(ij)=x=1ny=1mt|(x,y)nx×my×μ(t)

继续套路化简,注意这里x,y的意义改变了
i=1nj=1md(ij)=t=1min(n,m)x=1nty=1mtnxt×myt×μ(t)

然后就可以写成:
i=1nj=1md(ij)=t=1min(n,m)μ(t)x=1ntntxy=1mtmty

可以发现后面两个式子能等价于 ni=1ni 即前n个数约数个数的和,即 ni=1d(i) 。这个是可以线性筛出来的(后来才知道orz)。于是我们用 O(n) 的时间预处理 μ(i) d(i) 及其前缀和。
然后将这后面两个值相等的一起算,即来进行分块,就能在 O(n) 的时间内出解了。

嗯,这道题我经历了从11s到2s的蜕变。还是学了点东西的。
一开始不知道 d(i) 可以直接线性筛于是打了个分块计算,而且懒直接全部改成LL跑成了11s,然后把不必要的改回int后直接跳到了6s。但是我翻记录,发现大部分都是两秒多。看了别人的才知道 d(i) 是可以线性筛的。

//最后推荐一篇总结http://blog.csdn.net/loi_dqs/article/details/50539123
//屏蔽里的是原来求 d(i) 时的分块打法

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
#define N 50100

bool ispri[N];
int cnt,pri[N/4];LL mu[N],d[N],ct[N];
//ct[i]表示i的最小质因数的指数
//d[i]表示i的约数个数 [-后变其前缀和
int mymin(int x,int y){return (x<y)?x:y;}
void mobius(int lim)
{
    cnt=0;mu[1]=1;d[1]=1;
    for (int i=2;i<=lim;i++)
    {
        if (!ispri[i]) {pri[++cnt]=i;mu[i]=-1;d[i]=2;ct[i]=1;}
        for (int j=1;j<=cnt && i*pri[j]<=lim;j++)
        {
            int k=i*pri[j];
            ispri[k]=true;
            if (i%pri[j]==0)
            {
                mu[k]=0;ct[k]=ct[i]+1;
                d[k]=d[i]/(ct[i]+1)*(ct[k]+1);
                break;
            }
            mu[k]=-mu[i];
            ct[k]=1;d[k]=d[i]*2;
        }
    }
    for (int i=2;i<=lim;i++) mu[i]+=mu[i-1],d[i]+=d[i-1];
}
// void pre(int lim)
// {
    // int n,i,x,r;
    // for (n=1;n<=lim;n++)
    // {
        // d[n]=0;
        // for (i=1;i<=n;i=r+1)
        // {
            // x=n/i;r=mymin(n,n/x);
            // d[n]+=(LL)(r-i+1)*x;
        // }
    // }
// }
int main()
{
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
    int T,n,m,lim,x,y,lx,ly,i,r;
    scanf("%d",&T);
    mobius(50000);//pre(50000);
    while (T--)
    {
        scanf("%d%d",&n,&m);
        lim=mymin(n,m);LL ans=0;
        for (i=1;i<=lim;i=r+1)
        {
            x=n/i;y=m/i;
            lx=n/x;ly=m/y;
            r=mymin(lx,ly);
            ans+=d[x]*d[y]*(mu[r]-mu[i-1]);
        }
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值