定义:
若整数n除以整数d的余数为0,即d能整除n,则称d是n的约数,n是d的倍数,记为d|n。
算数基本定理的推论
一个大于1的正整数N,如果它的标准分解式为:
那么它的正因数个数为
它的全体正因数之和为
求N的正约数集合——试除法
若d>=sqrt(N)是N的约数,则N/d<=N也是N的约数。换言之,约数总是成对出现的(除了对于完全平方数,sqrt(N)会单独出现)。
所以,只需要扫描1~sqrt(N),尝试d能否整除N,若能整除,则N/d也是N的约数。时间复杂度O(sqrt(N))。
实现代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int h[N];
int main(){
int cnt=0;
int x;
cin>>x;
int y=sqrt(x);
for(int i=1;i<=y;i++){
if(x%i==0){
h[cnt++]=i;
if(i!=x/i) h[cnt++]=x/i;
}
}
sort(h,h+cnt);
for(int i=0;i<cnt;i++)cout<<h[i]<<" ";
cout<<endl;
return 0;
}
试除法的推论:
一个整数N的约数上限是2*sqrt(N)。
求1~N每个数的正约数集合——倍数法
若用“试除法”来求,时间复杂度过高,为O(N*sqrt(N))。
怎么解决?
可以这样考虑,对于每个数d,1~N中以d为约数的数就是d的倍数d,2d,3d……,floor(N/d)*d。
实现代码:
vector<int> factor[500010];
for(int i=1;i<=n;i++){
for(int j=1;j<=n/i;j++)
factor[i*j].push_back(i);
}
for(int i=1;i<=n;i++){
for(int j=0;j<factor[i].size();j++)
printf("%d ",factor[i][j]);
puts("");
}
时间复杂度O(N*logN);
倍数法的推论:
1~N每个数的约数个数的总和大约为N*logN。
最大公约数&最小公倍数
定义
最大公约数,指两个或多个整数共有约数中最大的一个。a,b的最大公约数记为gcd(a,b),同样的,a,b,c的最大公约数记为gcd(a,b,c),多个整数的最大公约数也有同样的记号。
两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数。整数a,b的最小公倍数记为lcm(a,b),同样的,a,b,c的最小公倍数记为lcm(a,b,c),多个整数的最小公倍数也有同样的记号。
定理
对于任意的自然数a,b,有:lcm(a,b)*gcd(a,b)=a*b
证明
设d=gcd(a,b),a0=a/d,b0=b/d。那么gcd(a0,b0)=1。(a0,b0互质)
所以lcm(a0,b0)=a0 * b0。
a0,b0同时扩大d倍,它们的最小公倍数也扩大d倍。(小学知识,相信你一定会 >_<)
于是lcm(a,b)=lcm(a0 * d,b0 * d)=lcm(a0,b0) * d=a0 * b0 * d=a * b/d。
证毕
求最大公约数
九章算术——更相减损术
原文:
可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。
翻译:
(如果需要对分数进行约分,那么)可以折半的话,就折半(也就是用2来约分)。如果不可以折半的话,那么就比较分母和分子的大小,用大数减去小数,互相减来减去,一直到减数与差相等为止,用这个相等的数字来约分。
即:
对于任意的自然数a,b且a>=b,有:gcd(a,b)=gcd(b,a-b)=gcd(a,a-b)
对于任意的自然数a,b,有:gcd(2a,2b)=2gcd(a,b)
证明:
设gcd(x,y)=d,则满足x=k1d,y=k2d,易得k1|k2。
情况1:x=y。显然,gcd(x,y)=x=gcd(x,0)=gcd(x,y-x)。
情况2:不妨令x<y,所以有k1<k2,所以y'=y-x=(k2-k1)*d。所以应证k1|(k2-k1)。
用反证法。令k3=k2-k1。
假设k1,k3存在公约数m(m>1),即k2=p2m,k3=k2-k1=p3m=(p2-p1)*m。
所以:k1=p1m,k2=p2m=(p3+p1)*m且p1|(p3+p1)。
要使k1|k2,所以m=1,与假设矛盾,所以k1|(k2-k1)。
所以原命题得证。
综上,gcd(x,y)=gcd(x,y-x)。
当然,此结论可用数学归纳法推广到一般,该性质对多个整数都成立。
代码实现:
#include<iostream>
using namespace std;
int main(){
int a,b;
cin>>a>>b;
while(a != b){
if(a > b)
a -= b;
else
b -= a;
}
cout<<a<<endl;
return 0;
}
辗转相除法(又名:欧几里德算法)
对于任意的自然数a,b,有:gcd(a,b)=gcd(b,a mod b)
证明:
若a<b,则gcd(a,b)=gcd(b,a mod b)=gcd(b,a),命题成立。
若a>=b,则:
(来自:百度百科)
比较:
更相减损术和辗转相除法的主要区别在于前者所使用的运算是“减”,后者是“除”。从算法思想上看,两者并没有本质上的区别,但是在计算过程中,如果遇到一个数很大,另一个数比较小的情况,可能要进行很多次减法才能达到一次除法的效果,从而使得算法的时间复杂度退化为O(N),其中N是原先的两个数中较大的一个。相比之下,辗转相除法的时间复杂度稳定于O(logN)。
但是由于高精度除法取模不易实现,在高精度情况下,优先考虑更相减损术。