原题链接
题目描述
- 设\(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;
}