bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

http://www.lydsy.com/JudgeOnline/problem.php?id=3994

 设d(x)为x的约数个数,给定N、M,求

 bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

 

用到的一个结论:

bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

证明:

枚举n的约数i,枚举m的约数j

那么i*j一定是n*m的约数

d(nm)相当于不同的i*j 的个数

若i, j 不互质

设gcd(i,j)= g , 则 i= p*g ,j=q*g

那么i*j 可以 组成两个互质数p*g*g 和 q 的乘积

p*g*g 和 q 也都输n和m的约数

即p*g*g 和 q 也都是合法的i,j

所以 互质数i和j的乘积组成了n*m的所有的约数

上式得证

 

回到这个题

令N<=M

   bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

化为

bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

改变枚举顺序,先枚举i,j

当n=[1,N]中所有i的倍数时,i会取n/i次

即i会取 bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和次    ( 里面有除号的[]表示下取整,下面一样)

 

j 同理

所以 上式化为

 bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

 

=  bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

=bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

改变枚举顺序,先枚举d

bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

令 bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

ans=bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和

f(x) 可以 用除法分块 提前 N*sqrt(N)处理好

预处理 μ 的前缀和

最后的式子 也可以用除法分块 在sqrt(N)时间内计算出

  

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

#define N 50001

typedef long long LL;

int prime[N];
bool vis[N];
int miu[N],sum[N];

LL f[N];

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

void premiu()
{
    int cnt=0;
    miu[1]=1;
    for(int i=2;i<N;++i)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            miu[i]=-1;
        }
        for(int j=1;j<=cnt;++j)
        {
            if(prime[j]*i>=N) break;
            vis[i*prime[j]]=true;
            if(i%prime[j]==0) break;
            miu[i*prime[j]]=-miu[i];
        }
    }
    for(int i=1;i<N;++i) sum[i]+=sum[i-1]+miu[i];
}

LL pref(int x)
{
    LL tot=0;
    int j;
    for(int i=1;i<=x;i=j+1)
    {
        j=x/(x/i);
        tot+=(LL)(j-i+1)*(x/i);
    }
    return tot;
}

int main()
{
    premiu();
    for(int i=1;i<N;++i) f[i]=pref(i);
    int T,n,m,j;
    LL ans; 
    read(T);
    while(T--)
    {
        ans=0;
        read(n); read(m);
        if(n>m) swap(n,m);
        for(int i=1;i<=n;i=j+1)
        {
            j=min(n/(n/i),m/(m/i));
            ans+=f[n/i]*f[m/i]*(sum[j]-sum[i-1]);
        } 
        cout<<ans<<'
';
    }
}