题目:
题解:
这个d(ij)怎么看都没有头绪,按照经验来说应该画成一个含有gcd的柿子
先看个结论
为什么呢?
证明一下,每一个nm的约数都可以表示为
i∗m/j
,
i|n
,
j|m
,这里要求
(i,j)=1
,我们考虑i代表取出n的约数i,m/j代表取出除了j的其他m的约数,如果
(i,j)!=1
,我们相当于t出去一个又拉回来一个,闲的没事干啊
那我们看看怎么求f(n)吧(只需要O(n√n) 分块预处理就好)
那我们用分块就可以O(n√n+T(√n+√m))就能顺利通过这道题啦
代码:
#include <cstdio>
#include <iostream>
#define LL long long
using namespace std;
const int N=50000;
int pri[N+5],num;
bool ss[N+5];LL f[N+5],mu[N+5];
void get_mu()
{
mu[1]=1;
for (int i=2;i<=N;i++)
{
if (!ss[i])
{
pri[++num]=i;
mu[i]=-1;
}
for (int j=1;j<=num && pri[j]*i<=N;j++)
{
ss[pri[j]*i]=1;
if (i%pri[j]==0) break;
mu[pri[j]*i]=-mu[i];
}
}
for (int i=1;i<=N;i++) mu[i]+=mu[i-1];
}
int main()
{
int T,i,n,m,j,k;
get_mu();
for (n=1;n<=N;n++)
for (j=1;j<=n;j=k+1)
{
k=min(n,n/(n/j));
f[n]+=(k-j+1)*(n/j);
}
scanf("%d",&T);
while (T--)
{
LL ans=0;
scanf("%d%d",&n,&m);
if (n>m) swap(n,m);
for (i=1;i<=n;i=j+1)
{
j=min(n,min(n/(n/i),m/(m/i)));
ans+=(mu[j]-mu[i-1])*f[n/i]*f[m/i];
}
printf("%lld\n",ans);
}
}