SDOI2015 约数个数和
题目链接:戳我
trick1——如何求约数个数和,变形
\[d(ij)=\sum_{u|i}\sum_{v|j}[gcd(u,v)=1]
\]
原式
\[=\sum_{i=1}^N\sum_{j=1}^M\sum_{u|i}\sum_{v|j}[gcd(u,v)=1]
\]
\[=\sum_{u=1}^N\sum_{v=1}^M\lfloor\frac{N}{u}\rfloor\lfloor\frac{M}{v}\rfloor[gcd(u,v)=1]
\]
\[=\sum_{i=1}^N\sum_{j=1}^M\lfloor\frac{N}{i}\rfloor\lfloor\frac{M}{j}\rfloor[gcd(i,j)=1]
\]
设\(f(x)=\sum_{i=1}^N\sum_{j=1}^M\lfloor\frac{N}{i}\rfloor\lfloor\frac{M}{j}\rfloor[gcd(i,j)=x]\)
\[F(x)=\sum_{x|d}f(d)
\]
\[F(x)=\sum_{x|d}\sum_{i=1}^N\sum_{j=1}^M\lfloor\frac{N}{i}\rfloor\lfloor\frac{M}{j}\rfloor[gcd(i,j)=d]
\]
trick2——消掉gcd
\[F(x)=\sum_{i=1}^N\sum_{j=1}^M\lfloor\frac{N}{i}\rfloor\lfloor\frac{M}{j}\rfloor[x|gcd(i,j)]
\]
\[=F(x)=\sum_{i=1}^{\lfloor\frac{N}{x}\rfloor}\sum_{j=1}^{\lfloor\frac{M}{x}\rfloor}\lfloor\frac{N}{ix}\rfloor\lfloor\frac{M}{jx}\rfloor
\]
我们需要的是\(f(1)\)
\[f(1)=\sum_{d=1}^{min(N,M)}\mu(d)F(d)
\]
\[f(1)=\sum_{d=1}^{min(N,M)}\mu(d)\sum_{i=1}^{\lfloor\frac{N}{d}\rfloor}\lfloor\frac{N}{id}\rfloor\sum_{j=1}^{\lfloor\frac{M}{d}\rfloor}\lfloor\frac{M}{jd}\rfloor
\]
trick3——预处理\(\sum_{i=1}^{n}\lfloor\frac{n}{i}\rfloor\)
设\(g[i]=\sum_{x=1}^i\lfloor\frac{i}{x}\rfloor\)
那么有
\[f(1)=\sum_{d=1}^{min(n,m)}\mu(d)\times g[n/d]\times g[m/d]
\]
直接数论分块即可。
关于公式的证明:
我没有严格公式化证明——
我们可以发现,其实拼出来的每个数乘上他们的gcd,就是原先的因子,也就是互质形态相乘再乘上gcd的平方。现在需要解决的是这个值原先有没有被计算过,当然这个平方一个分配一个就是我们处理的原数,自然没有被计算过。而两个gcd放在一起乘上其中一个数,又因为我们的i,j的因子都是从小到大枚举的,所以大于当前数的数自然是没有被枚举计算过。
然后严格证明捞了一位大佬的证明如下——
代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define MAXN 50010
int T,n,m,cnt;
int mu[MAXN],sum[MAXN],not_prime[MAXN],prime[MAXN];
long long g[MAXN];
inline int min(int x,int y){return x>y?y:x;}
inline void get_mu()
{
//not_prime[1]=1;
mu[1]=1;
for(int i=2;i<=50000;i++)
{
if(!not_prime[i]){prime[++cnt]=i;mu[i]=-1;}
for(int j=1;j<=cnt&&prime[j]*i<=50000;j++)
{
not_prime[prime[j]*i]=1;
if(i%prime[j]==0) break;
else mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=50000;i++) sum[i]=sum[i-1]+mu[i];
for(int i=1;i<=50000;i++)
{
long long cur_ans=0;
for(int l=1,r;l<=i;l=r+1)
r=(i/(i/l)),cur_ans+=1ll*(r-l+1)*(i/l);
g[i]=cur_ans;
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
scanf("%d",&T);
get_mu();
//for(int i=1;i<=10;i++) printf("%d\n",mu[i]);
//for(int i=1;i<=10;i++) printf("%d\n",sum[i]);
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
long long ans=0;
for(int l=1,r;l<=min(n,m);l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans+=1ll*(sum[r]-sum[l-1])*g[n/l]*g[m/l];
}
printf("%lld\n",ans);
}
}