[SDOI2015]约数个数和
题目描述
- 设\(d(x)\)为\(x\)的约数个数,给定\(N、M\),求\(\sum_{i=1}^{N}\sum_{j=1}^{m}d(ij)\)
输入输出格式
- 输入格式:
- 输入文件包含多组测试数据。第一行,一个整数T,表示测试数据的组数。接下来的T行,每行两个整数N、M。
- 输出格式:
- T行,每行一个整数,表示你所求的答案。
解题思路
- 这道题,如果是第一次做,或者是不了解\(d(x)\)这个约数个数函数的某些神奇性质,那么是很难往下面继续推的。
(我也是最近才知道的2333) - 很显然,这题的难点就是这个约数个数函数。因此,我先给出这个函数的一个重要性质:
\[d(ij)=\sum_{x|i}\sum_{y|j}[gcd(x,y)=1]
\]
至于具体的证明,还是感性的理解一下吧。
- 知道这个式子后,我们就可以开始尝试去推了。
- 我们极其套路的去设几个函数(PS:如果不知道为什么要这样设,可以去看一看YY的GCD):
\[f(d)=\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)=d]
\]
\[F(n)=\sum_{n|d}f(d)
\]
由莫比乌斯反演可以得到:
\[f(n)=\sum_{n|d}\mu(\lfloor\frac{d}{n}\rfloor)F(d)
\]
我们所求的为Ans:
\[Ans=\sum_{i=1}^{n}\sum_{j=1}^{m}d(ij)
\]
\[Ans=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{x|i}\sum_{y|j}[gcd(x,y)=1]
\]
看到这个\([gcd(x,y)=1]\),我们就可以根据\(\mu\)的性质把它带进去,如果不知道的话可以去看看我写的莫比乌斯反演
\[Ans=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{x|i}\sum_{y|j}\sum_{d|gcd(x,y)}\mu(d)
\]
更换枚举项,由枚举\(gcd(x,y)\)的约数,改为直接枚举\(d\)
\[Ans=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{x|i}\sum_{y|j}\sum_{d=1}^{min(n,m)}\mu(d)*[d|gcd(x,y)]
\]
将\(\mu(d)\)可以提出来,因为它与\(i,j\)无关
\[Ans=\sum_{d=1}^{min(n,m)}\mu(d)\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{x|i}\sum_{y|j}[d|gcd(x,y)]
\]
接着,由枚举\(i,j\)和它们的约数改变为枚举它们的约数再直接乘上这些约数的倍数的个数。因为每一个约数都会对它的倍数产生贡献。
\[Ans=\sum_{d=1}^{min(n,m)}\mu(d)\sum_{x=1}^{n}\sum_{y=1}^{m}[d|gcd(x,y)]\lfloor\frac{n}{x}\rfloor\lfloor\frac{m}{y}\rfloor
\]
我们再一次更换枚举项,将枚举\(x,y\)换为枚举\(dx,dy\)。这样\([d|gcd(x,y)]\)这个条件就可以省去。
\[Ans=\sum_{d=1}^{min(n,m)}\mu(d)\sum_{x=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{y=1}^{{\lfloor\frac{m}{d}\rfloor}}\lfloor\frac{n}{dx}\rfloor\lfloor\frac{m}{dy}\rfloor
\]
我们可以发现\(\lfloor\frac{n}{dx}\rfloor\)与\(y\)无关,所以可以提前。
\[Ans=\sum_{d=1}^{min(n,m)}\mu(d)(\sum_{x=1}^{\lfloor\frac{n}{d}\rfloor}\lfloor\frac{n}{dx}\rfloor)(\sum_{y=1}^{{\lfloor\frac{m}{d}\rfloor}}\lfloor\frac{m}{dy}\rfloor)
\]
当我们将式子化简成这样的时候,我们已经可以看出,这个式子已经可以做到\(O(n)\)计算了。但是,由于存在多组数据,所以我们就可以运用整除分块,将这个式子优化成\(O(\sqrt{n})\)的时间复杂度。(如果不知道整除分块,可以去看看我写的整除分块)
- 这样,这道题就可以A了。
还是贴一下我的代码吧
#include<bits/stdc++.h>
#define N 50100
using namespace std;
inline void read(int &x)
{
x=0;
static int p;p=1;
static char c;c=getchar();
while(!isdigit(c)){if(c=='-')p=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+(c-48);c=getchar();}
x*=p;
}
bool vis[N];
int prim[N],cnt,mu[N],sum[N];
long long g[N];
void get_mu(int n)
{
mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i]){prim[++cnt]=i;mu[i]=-1;}
for(int j=1;j<=cnt&&prim[j]*i<=n;j++)
{
vis[prim[j]*i]=1;
if(i%prim[j]==0)break;
else mu[i*prim[j]]=-mu[i];
}
}
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+mu[i];
for(int i=1;i<=n;i++)
{
long long ans=0;
for(int l=1,r;l<=i;l=r+1)
{
r=(i/(i/l));
ans+=1ll*(r-l+1)*1ll*(i/l);
}
g[i]=ans;
}
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int t;
read(t);
get_mu(50000);
while(t--)
{
static int n,m;
read(n);read(m);
static int max_rep;max_rep=min(n,m);
static long long ans;ans=0;
for(int l=1,r;l<=max_rep;l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans+=(sum[r]-sum[l-1])*1ll*g[n/l]*1ll*g[m/l];
}
printf("%lld\n",ans);
}
return 0;
}