题目描述
传送门
题目大意:
∑pmin(n,m)∑i=1n∑j=1m[gcd(i,j)=p]
其中p是质数。
题解
这道题还是要用到反演啊
如果
F(n)=∑n|df(d)
,那么
f(n)=∑n|dμ(dn)∗F(d)
设
f(d)=∑ni=1∑mj=1[gcd(i,j)=d]
表示满足
gcd(i,j)=d
且
1<=i<=n,1<=j<=m
的
(i,j)
的对数
F(d)=∑d|nf(n)
表示满足
d|gcd(i,j)
且
1<=i<=n,1<=j<=m
的
(i,j)
的对数
F(d)=⌊nd⌋∗⌊md⌋
设n<=m,那么上面的式子就转化成了
∑pmin(n,m)∑p|dμ(dp)∗F(d)
∑pmin(n,m)∑d=1nμ(d)∗F(d∗p)
然后令T=d*p,改变枚举的顺序
∑T=1n⌊nT⌋∗⌊mT⌋∑p|Tμ(Tp)
其中p是质数
那么如果我们能预处理 ∑p|Tμ(Tp) ,这个问题就能在 O(n√+m−−√)
我们可以直接暴力枚举质数p,然后用p去更新所有范围内p的倍数。
貌似n内质数的个数是接近 n/logn ,对于每个素数来说,枚举倍数的均摊复杂度是 O(logn) ,所以暴力预处理的复杂度是接近O(n)的。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 10000000
#define LL long long
using namespace std;
int prime[N+3],mu[N+3];
LL f[N+3];
bool pd[N+3];
int n,m,T;
void init()
{
mu[1]=1;
for (int i=2;i<=N;i++) {
if (!pd[i]) {
prime[++prime[0]]=i;
mu[i]=-1;
}
for (int j=1;j<=prime[0];j++) {
if (i*prime[j]>N) break;
pd[i*prime[j]]=1;
if (i%prime[j]==0) {
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
for (int i=1;i<=prime[0];i++) {
int t=prime[i];
for (int j=1;j*t<=N;j++) f[j*t]+=mu[j];
}
for (int i=1;i<=N;i++) f[i]=f[i-1]+f[i];
}
int main()
{
freopen("a.in","r",stdin);
freopen("my.out","w",stdout);
scanf("%d",&T);
init();
while (T--) {
scanf("%d%d",&n,&m);
if (n>m) swap(n,m);
int j=0; LL ans=0;
for (int i=1;i<=n;i=j+1) {
j=min(n/(n/i),m/(m/i));
ans=ans+(f[j]-f[i-1])*(n/i)*(m/i);
}
printf("%lld\n",ans);
}
}