P1029 [NOIP2001 普及组] 最大公约数和最小公倍数问题

P1029 [NOIP2001 普及组] 最大公约数和最小公倍数问题


前备知识

最大公约数(GCD):

设有 a , b ( a , b ∈ Z + ) a,b(a,b∈Z+) a,b(a,bZ+).在所有的小于等于 m i n ( a , b ) min(a,b) min(a,b)的数必有一数 c ( c ∈ Z + ) c(c∈Z+) c(cZ+),使得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,bZ+).在所有的大于等于 m a x ( a , b ) max(a,b) max(a,b)的数必有一数 c ( c ∈ Z + ) c(c∈Z+) c(cZ+),使得 a ︱ c a︱c ac b ︱ c b︱c bc.且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} 2x,y105.那么我们就能自然而然的想到 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! Thatsit?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),1js)以及 [ 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),1js)

那么(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}} p1a1p1b1p2a2p2b2...psaspsbs

= 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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值