P1029 [NOIP2001 普及组] 最大公约数和最小公倍数问题
前备知识
最大公约数(GCD):
设有 a , b ( a , b ∈ Z + ) a,b(a,b∈Z+) a,b(a,b∈Z+).在所有的小于等于 m i n ( a , b ) min(a,b) min(a,b)的数必有一数 c ( c ∈ Z + ) c(c∈Z+) c(c∈Z+),使得c︱a且c︱b.且c是所有此类数中最大的,那么称 c c c为 a , b a,b a,b的最大公约数.记作 ( a , b ) (a,b) (a,b).
最小公倍数(LCM):
设有 a , b ( a , b ∈ Z + ) a,b(a,b∈Z+) a,b(a,b∈Z+).在所有的大于等于 m a x ( a , b ) max(a,b) max(a,b)的数必有一数 c ( c ∈ Z + ) c(c∈Z+) c(c∈Z+),使得 a ︱ c a︱c a︱c且 b ︱ c b︱c b︱c.且c是所有此类数中最小的,那么称 c c c为 a , b a,b a,b的最小公倍数.记作 [ a , b ] [a,b] [a,b].
定理:
(a,b)[a,b]=ab.(证明见附文)
求两数之GCD:
较常用的有辗转相除法:有定理(a mod b,b)=(a,b) 即a模b与b的GCD等于a,b的GCD.我们就可以充分利用这一点,来缩小问题规模,求出GCD复杂度为O(log max(a,b)),几乎可以省略(在相近规模下能让辗转相除法执行最多的是两个相邻的斐波那契数)
code:
int gcd(int a,int b){
if(a%b==0) return b;
else return gcd(b,a%b);
}
下面是用三目运算符压行的版本:
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
求两数之LCM:
充分利用定理 ( a , b ) [ a , b ] = a b (a,b)[a,b]=ab (a,b)[a,b]=ab,得出 [ a , b ] = a b / ( a , b ) [a,b]=ab/(a,b) [a,b]=ab/(a,b).则可得出代码:
int lcm(int a,int b){
return (a*b/gcd(a,b));
}
正文,启动!
这道题问我们有多少组二元集 P , Q {P,Q} P,Q,使得 ( P , Q ) = x (P,Q)=x (P,Q)=x, [ P , Q ] = y [P,Q]=y [P,Q]=y.规模比较小, 2 ≤ x , y ≤ 1 0 5 2≤x,y≤10^{5} 2≤x,y≤105.那么我们就能自然而然的想到 O ( n ) O(n) O(n)的枚举算法.枚举谁呢?完全不需要枚举Q,因为我完全就可以通过P来求出Q,即Q=xy/p.如果P,Q并非正确值,那么(P,Q)!=x,[P,Q]!=y,我们则只需要验证一下(P,Q)==x&&[P,Q]==y是否为真,就可以确定P,Q是否为正确值.P的枚举范围又是多少? 2到1e5ma?不,其实只需要x到y即可,应为P要以x为约数,以y为倍数,那么必然有x≤P,Q≤y.
AC代码奉上:
#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int lcm(int a,int b){
return (a/gcd(a,b)*b);//防止溢出
}
int x,y;
int ans;
int p,q;
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
cin>>x>>y;
for(p=x;p<=y;p++){
q=x*y/p;
if(gcd(p,q)==x&&lcm(p,q)==y){
//验算
ans++;
}
}
cout<<ans<<'\n';
return 0;
}
T
h
a
t
′
s
i
t
?
N
O
!
That's it?NO!
That′sit?NO!
在最劣情况下,该算法会被执行1e6-2次,而我们则有一种最劣时只需被枚举1e3次的
O
(
√
n
)
O(√n)
O(√n)算法.为什么不枚举y的所有约数呢?y既然为
[
P
,
Q
]
[P,Q]
[P,Q],那么P必然是y的约数,同时我们再计算出Q,验证P,Q是否满足条件即可.
Code:
#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int x,y;
int cnt;
int p,q;
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
cin>>x>>y;
for(int k=1;k<=sqrt(y);k++){
if(y%k==0){
if(gcd(k,y/k*x)==x){
//写为y/k*x,防止溢出
cnt++;
}
if(y/k!=k){//注意重复判断
if(gcd(y/k,k*x)==x){
cnt++;//另一组是p=y/k,q=k*x
}
}
}
}
cout<<cnt<<'\n';
return 0;
}
同理,我们也可以得出一种枚举x的倍数的算法,但其时间复杂度为
O
(
x
/
y
)
O({x}/{y})
O(x/y),
如下(其实就是做法一的PLUS版):
#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int lcm(int a,int b){
return (a/gcd(a,b)*b);//防止溢出
}
int x,y;
int ans;
int p,q;
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
cin>>x>>y;
for(int i=1;i*x<=y;i++){
p=i*x;
q=x*y/p;
if(gcd(p,q)==x&&lcm(p,q)==y){
//验算
ans++;
}
}
cout<<ans<<'/n';
return 0;
}
总结:这道题是一道不错的枚举模拟题.对于某个题究竟枚举什么是最优的,还是要从数据范围,题意,编程复杂度来多方面考虑,再进行取舍.例如当数据范围小于等于29时可以大胆考虑枚举子集的 O ( 2 n ) O(2^n) O(2n)算法.小于等于1e4时为 n 2 n^{2} n2算法,小于等于1e8时,为n.大于1e8,就要考虑优化的 l o g n log n logn或 √ n √n √n算法了
附文:
(
a
,
b
)
[
a
,
b
]
=
a
b
(a,b)[a,b]=ab
(a,b)[a,b]=ab的证明设
a
>
1
a>1
a>1,那么必有
a
=
p
1
a
1
+
p
2
a
2
+
.
.
.
+
p
s
a
s
a=p_{1}^{a_{1}}+p_{2}^{a_{2}}+...+p_{s}^{a_{s}}
a=p1a1+p2a2+...+psas,其中
p
j
p_j
pj(1≤j≤s)是两两不相同的质数,
a
j
a_j
aj(1≤j≤s)表示对应质数的次幂,若在不记次序的意义下,该分解式是唯一的.(其实就是分解质因数)则可得出:
若
a
=
p
1
a
1
+
p
2
a
2
+
.
.
.
+
p
s
a
s
,
b
=
p
1
b
1
+
p
2
b
2
+
.
.
.
+
p
s
b
s
a=p_{1}^{a_{1}}+p_{2}^{a_{2}}+...+p_{s}^{a_{s}},b=p_{1}^{b_{1}}+p_{2}^{b_{2}}+...+p_{s}^{b_{s}}
a=p1a1+p2a2+...+psas,b=p1b1+p2b2+...+psbs(这里允许某些
a
j
a_{j}
aj或
b
j
b_{j}
bj为0),
那么
(
a
,
b
)
=
p
1
z
1
+
p
2
z
2
+
.
.
.
+
p
s
z
s
(a,b)=p_{1}^{z_{1}}+p_{2}^{z_{2}}+...+p_{s}^{z_{s}}
(a,b)=p1z1+p2z2+...+pszs(
z
j
=
m
i
n
(
a
j
,
b
j
)
,
1
≤
j
≤
s
z_{j}=min(a_{j},b_{j}),1≤j≤s
zj=min(aj,bj),1≤j≤s)以及
[
a
,
b
]
=
p
1
g
1
+
p
2
g
2
+
.
.
.
+
p
s
g
s
[a,b]=p_{1}^{g_{1}}+p_{2}^{g_{2}}+...+p_{s}^{g_{s}}
[a,b]=p1g1+p2g2+...+psgs(
g
j
=
m
a
x
(
a
j
,
b
j
)
,
1
≤
j
≤
s
g_{j}=max(a_{j},b_{j}),1≤j≤s
gj=max(aj,bj),1≤j≤s)
那么(a,b)[a,b]= p 1 z 1 + g 1 + p 2 z 1 + g 2 + . . . + p s z s + g s p_{1}^{z_{1}+g_{1}}+p_{2}^{z_{1}+g_{2}}+...+p_{s}^{z_{s}+g_{s}} p1z1+g1+p2z1+g2+...+pszs+gs
= p 1 m i n ( a 1 , b 1 ) + m a x ( a 1 , b 1 ) + p 2 m i n ( a 2 , b 2 ) + m a x ( a 2 , b 2 ) ) + . . . + p s m i n ( a s , b s ) + m a x ( a s , b s ) p_{1}^{min(a_{1},b_{1})+max(a_{1},b_{1})}+p_{2}^{min(a_{2},b_{2})+max(a_{2},b_{2}))}+...+p_{s}^{min(a_{s},b_{s})+max(a_{s},b_{s})} p1min(a1,b1)+max(a1,b1)+p2min(a2,b2)+max(a2,b2))+...+psmin(as,bs)+max(as,bs)
= p 1 a 1 + b 1 + p 2 a 2 + b 2 + . . . p s a s + b s p_{1}^{a_{1}+b_{1}}+p_{2}^{a_{2}+b_{2}}+...p_{s}^{a_{s}+b_{s}} p1a1+b1+p2a2+b2+...psas+bs
= p 1 a 1 ∗ p 1 b 1 ∗ p 2 a 2 ∗ p 2 b 2 ∗ . . . p s a s ∗ p s b s p_{1}^{a_{1}}*p_{1}^{b_{1}}*p_{2}^{a_{2}}*p_{2}^{b_{2}}*...p_{s}^{a_{s}}*p_{s}^{b_{s}} p1a1∗p1b1∗p2a2∗p2b2∗...psas∗psbs
= a b ab ab
所以说 ( a , b ) [ a , b ] = a b (a,b)[a,b]=ab (a,b)[a,b]=ab的本质就是 m i n ( a , b ) + n a x ( a , b ) = a + b min(a,b)+nax(a,b)=a+b min(a,b)+nax(a,b)=a+b;