题意:给定 n , m n,m n,m,问满足 1 < = x < = n a n d 1 < = y < = m 1<=x<=n \space and \space 1<=y<=m 1<=x<=n and 1<=y<=m且 g c d ( x , y ) gcd(x,y) gcd(x,y)为素数的方案数有多少
思路:关注此题很久了,今天借了高爷的账号终于补掉了此题(再次表示感谢qwq)
我们试着来推导一下公式。
设:
f
(
i
)
:
g
c
d
(
x
,
y
)
=
i
f(i):gcd(x,y) = i
f(i):gcd(x,y)=i 的方案数。
F
(
i
)
:
i
∣
g
c
d
(
x
,
y
)
F(i):i|gcd(x,y)
F(i):i∣gcd(x,y) 的方案数。
易得:
F
(
n
)
=
∑
n
∣
d
f
(
d
)
⇒
f
(
n
)
=
∑
n
∣
d
μ
(
d
n
)
F
(
d
)
F(n) = \sum_{n|d}f(d) ⇒ f(n) = \sum_{n|d} \mu(\frac{d}{n})F(d)
F(n)=n∣d∑f(d)⇒f(n)=n∣d∑μ(nd)F(d)
又因为
F
(
d
)
=
⌊
n
d
⌋
⌊
m
d
⌋
F(d) = \lfloor\frac{n}{d}\rfloor \lfloor\frac{m}{d}\rfloor
F(d)=⌊dn⌋⌊dm⌋
得:
f
(
n
)
=
∑
n
∣
d
μ
(
d
n
)
⌊
n
d
⌋
⌊
m
d
⌋
f(n) = \sum_{n|d} \mu(\frac{d}{n})\lfloor\frac{n}{d}\rfloor \lfloor\frac{m}{d}\rfloor
f(n)=n∣d∑μ(nd)⌊dn⌋⌊dm⌋
此时我们可以枚举质数
p
p
p,设答案为
A
n
s
Ans
Ans,则:此处
d
d
d是枚举
p
p
p的倍数
A
n
s
=
∑
p
m
i
n
(
n
,
m
)
∑
d
m
i
n
(
n
,
m
)
μ
(
d
)
⌊
n
p
d
⌋
⌊
m
p
d
⌋
Ans = \sum_{p}^{min(n,m)} \sum_{d}^{min(n,m)} \mu(d)\lfloor\frac{n}{pd}\rfloor \lfloor\frac{m}{pd}\rfloor
Ans=p∑min(n,m)d∑min(n,m)μ(d)⌊pdn⌋⌊pdm⌋
但这么做还是会T的,我们可以考虑进一步优化。
令
T
=
p
d
T = pd
T=pd
此时我们转换一个思路,可不可以直接枚举
T
T
T来进行优化复杂度。因为
T
T
T的范围一定也是
[
1
,
m
i
n
(
n
,
m
)
]
[1,min(n,m)]
[1,min(n,m)]
故答案可写成:
A
n
s
=
∑
T
m
i
n
(
n
,
m
)
⌊
n
T
⌋
⌊
m
T
⌋
∑
p
∣
T
μ
(
T
p
)
Ans = \sum_{T}^{min(n,m)}\lfloor\frac{n}{T}\rfloor \lfloor\frac{m}{T}\rfloor \sum_{p|T}\mu(\frac{T}{p})
Ans=T∑min(n,m)⌊Tn⌋⌊Tm⌋p∣T∑μ(pT)
此时后面的约数莫比乌斯函数之和可以用预处理前缀和去维护。
这样我们就能在
O
(
n
)
O(\sqrt n)
O(n)的复杂度得到答案。
但预处理时枚举素数需要
O
(
n
/
n
)
O(n/\sqrt n)
O(n/n)而枚举倍数平摊下来是
O
(
n
)
O(\sqrt n)
O(n)的复杂度。
故总复杂度:
O
(
n
)
O(n)
O(n)
此处还有一个小优化:可以在线性筛时同时处理约数莫比乌斯函数之和。可减少常数。
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 1e7 + 10;
int pri[A],mu[A],tot,sum[A];
bool vis[A];
void init(){
tot = 0;
mu[1] = vis[1] = vis[0] = 1;
for(int i=2 ;i<A ;i++){
if(!vis[i]){pri[++tot] = i;mu[i] = -1;}
for(int j=1 ;j<=tot&&i*pri[j]<A ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){
mu[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]<A ;j++){
sum[j*pri[i]] += mu[j];
}
}
for(int i=1 ;i<A ;i++) sum[i] += sum[i-1];
}
int main(){
init();
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
int last;
ll ans = 0;
for(int i=1 ;i<=min(n,m) ;i=last+1){
last = min(n/(n/i),m/(m/i));
ans += 1LL*(n/i)*(m/i)*(sum[last]-sum[i-1]);
}
printf("%lld\n",ans);
}
return 0;
}
(优化)
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 1e7 + 10;
int pri[A],mu[A],tot,sum[A];
bool vis[A];
void init(){
tot = 0;
mu[1] = vis[1] = vis[0] = 1;
for(int i=2 ;i<A ;i++){
if(!vis[i]){pri[++tot] = i;mu[i] = -1;sum[i]=1;}
for(int j=1 ;j<=tot&&i*pri[j]<A ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){
mu[i*pri[j]] = 0;
sum[i*pri[j]] = mu[i];
break;
}
mu[i*pri[j]] = -mu[i];
sum[i*pri[j]] = mu[i] - sum[i];
}
}
for(int i=1 ;i<A ;i++) sum[i] += sum[i-1];
}
int main(){
init();
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
int last;
ll ans = 0;
for(int i=1 ;i<=min(n,m) ;i=last+1){
last = min(n/(n/i),m/(m/i));
ans += 1LL*(n/i)*(m/i)*(sum[last]-sum[i-1]);
}
printf("%lld\n",ans);
}
return 0;
}