携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情
[SDOI2015]约数个数和
题目描述
设 为 的约数个数,给定 ,求
输入格式
输入文件包含多组测试数据。
第一行,一个整数 ,表示测试数据的组数。
接下来的 行,每行两个整数 。
输出格式
行,每行一个整数,表示你所求的答案。
样例 #1
样例输入 #1
2
7 4
5 6
样例输出 #1
110
121
提示
【数据范围】
对于 的数据,。
考虑一个质因数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);
}
}