【数学思维】约数个数和

75 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情

[SDOI2015]约数个数和

题目描述

d(x)d(x)xx 的约数个数,给定 n,mn,m,求
i=1nj=1md(ij)\sum_{i=1}^n\sum_{j=1}^md(ij)

输入格式

输入文件包含多组测试数据。
第一行,一个整数 TT,表示测试数据的组数。
接下来的 TT 行,每行两个整数 n,mn,m

输出格式

TT 行,每行一个整数,表示你所求的答案。

样例 #1

样例输入 #1

2
7 4
5 6

样例输出 #1

110
121

提示

【数据范围】
对于 100%100\% 的数据,1T,n,m500001\le T,n,m \le 50000

考虑一个质因数p

设n有a1个p,m有a2个p,那么p的选择方案数就是a1+a2+1。

相当于sigma:i=[0..a1],j=[0..a2] [gcd(p^i,p^j)=1]

质因数之间可以任意组合,

所以得到d(n*m)=sigma:i|n,j|m [gcd(i,j)=1] ans=sigma:i=1..n,j=1..m,d1|i,d2|j [gcd(d1,d2)=1] 把[gcd(d1,d2)=1]用sigma:d|d1,d|d2 miu(d)代掉 =d1|i,d2|j,d|d1,d|d2 miu(d) 考虑对每个d,d1的个数。

d1得是d的倍数,i的因数。

设d1=kd,那么有kd|i,k|i/d。 所以d1的个数就是i/d的因数个数,记作num。

ans=sigma:i=1..n,j=1..m,d|i,d|j miu(d)*num(i/d)*num(j/d) 考虑对每个d,i的贡献。

i得是d的倍数,每个i贡献为num(i/d)。

还是设i=kd。那么kd<=n,k<=n/d。

所以i的贡献就是num(1)+..num(n/d),记作s(n/d)。

ans=sigma:d=1..min(n,m) miu(d)*s(n/d)*s(m/d)

线性筛预处理num,miu,处理前缀和,之后分块。

#include<bits/stdc++.h>
using std::min;

typedef long long ll;
const int N=50000;
int miu[N+2],s[N+5];bool vis[N+5];
int big[N+2];//min_pirme(i)^big[i]|i
int p[N/9],top,i,j,x,y; 
void shai()
{
    miu[1]=s[1]=1;
    for(i=2;i<=N;++i)
    {
        if(!vis[i]) 
        { p[++top]=i;
          miu[i]=-1;
          s[i]=2;
          big[i]=i; 
        }
        for(j=1;(y=i*(x=p[j]))<=N;++j)
        {
            vis[y]=1;
            if(!(i%x)) 
            {
                big[y]=big[i]*x;
              if(i==big[i]) s[y]=s[i]+1;
              else s[y]=s[i/big[i]]*s[x*big[i]];
              break;
            }
            big[y]=x;
            s[y]=s[i]*s[x];
            miu[y]=miu[i]*miu[x];
        }
    }
    for(i=1;i<=N;++i) {s[i]+=s[i-1];miu[i]+=miu[i-1];}
}

int main()
{
    freopen("1.in","r",stdin);
    shai();
    int tt;scanf("%d",&tt);
    while(tt--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        i=1;
        int mi=min(n,m);
        ll ans=0;
        while(i<=mi)
        {
            int j=min(n/(n/i),m/(m/i));
            ans+=(ll)(miu[j]-miu[i-1])*s[n/i]*s[m/i];
            i=j+1;
        }
        printf("%lld\n",ans);
    }
}