定义:

若整数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)。

但是由于高精度除法取模不易实现,在高精度情况下,优先考虑更相减损术。

02-12 17:41