跟着litble学套路
Solution
题目要求 ∑ni=1∑mj=1[gcd(i,j)∈prime] ∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) ∈ p r i m e ]
开始疯狂套路
0x00
首先枚举
gcd
g
c
d
0x01
接着将
gcd(a,b)==d
g
c
d
(
a
,
b
)
==
d
变为
gcd(a/d,b/d)==1
g
c
d
(
a
/
d
,
b
/
d
)
==
1
0x02
其次我们通过莫比乌斯函数的性质
将 [gcd(i,j)==1] [ g c d ( i , j ) == 1 ] 转化为 ∑t|gcd(i,j)μ(t) ∑ t | g c d ( i , j ) μ ( t ) ,即 ∑t|i∧t|jμ(t) ∑ t | i ∧ t | j μ ( t )
0x03
下一步枚举
t
t
同时也会变形,变为枚举
t
t
的倍数
0x04
设
T=td
T
=
t
d
,枚举
T
T
0x05
设
F(T)=∑nd∈prime∧d|Tμ(Td)
F
(
T
)
=
∑
d
∈
p
r
i
m
e
∧
d
|
T
n
μ
(
T
d
)
,用线性筛将其处理出来
后面利用分块+前缀和在 O(n−−√) O ( n ) 时间内处理出来
搞定!
代码如下
#include <bits/stdc++.h>
using namespace std;
const int N = 10000005;
int pri[N],tot,mu[N],n,m,t;
long long f[N];
bool mark[N];
void get() {
mu[1]=1;
for(int i=2;i<=10000000;++i) {
if(!mark[i]) {
pri[++tot]=i;
mu[i]=-1;
}
for(int j=1;j<=tot && pri[j]*i<=10000000;++j) {
mark[i*pri[j]]=1;
if(i%pri[j]==0) break;
mu[i*pri[j]]=-mu[i];
}
}
for(int i=1;i<=tot;++i)
for(int j=1;j*pri[i]<=10000000;++j)
f[pri[i]*j]+=mu[j];
for(int i=1;i<=10000000;++i)
f[i]+=f[i-1];
}
long long cal() {
scanf("%d%d",&n,&m);
if(n>m) swap(n,m);
int pos=0;
long long ans=0;
for(int i=1;i<=n;i=pos+1) {
pos=min(n/(n/i),m/(m/i));
ans+=(f[pos]-f[i-1])*(n/i)*(m/i);
}
return ans;
}
int main() {
get();
scanf("%d",&t);
while(t--)
printf("%lld\n",cal());
return 0;
}