首页 > 代码库 > 10.1数论初步

10.1数论初步

1.欧几里得算法(辗转相除法)和唯一分解定理:

①唯一性分解定理:

算术基本定理,又称为正整数的唯一分解定理,即:每个大于1的自然数均可写为质数的积,而且这些素因子按大小排列之后,写法仅有一种方式。

算术基本定理的内容由两部分构成:
分解的存在性;
分解的唯一性,即若不考虑排列的顺序,正整数分解为素数乘积的方式是唯一的。

②辗转相除法:

是求最大公约数的算法。

辗转相除法基于如下原理:两个整数的最大公约数等于其中较小的数和两数求余数的最大公约数。在这个过程中,较大的数缩小了,所以继续进行同样的计算可以不断缩小这两个数直至余数变为零。这时的除数就是所求的两个数的最大公约数。

辗转相除法处理大数时非常高效,它需要的步骤不会超过较小数的位数(十进制下)的五倍。

③例题(除法表达式):

【描述】给出如下除法表达式E:X1/X2/X3/..../Xk,其中Xi是正整数并且Xi<=2 000 000 000(1<=i<=k,k<=10 000)。除法表达式应当按照从左到右的顺序求结果。

例如:表达式1/2/1/2的值是1/4。现在让你可以在表达E中嵌入括号以改变计算顺序,例如表达式(1/2)/(1/2)的值是1。
现在给你一个除法表达式E,要求告诉是否能够通过加括号(或者不加)得到表达式E‘ ,E‘的值为整数。

【思路】首先优化算法,表达式的值一定可以写成A/B的形式:A是一些Xi的乘积,而B是其他数的乘积,不难发现,X2必须放在分母位置,其他数随意,因为本题是存在性求解问题,所以只要极端情况下存在解即可,极端情况是只有X2在分母上,其他数都在分子上的情况,这样能保证分母最大,分子最小,以下是算法的优化过程:

1.进行高精度运算,k次乘法+1次除法,明显会杯具的TLE

2.利用唯一性分解定理:把x2写成若干个素数相乘的形式:X2 = p1^a1*p2^a2*p3^a3*……,依次判断每个pi^ai是否是X1X2X3X4……的约数,只需要把Xi中的pi的指数加起来,如果比ai打,说明pi约掉了,E是整数,以下是部分代码实现,my ugly code:

#include<stdio.h>
#include<iostream>
using namespace std;
struct num
{
    int p,a;
};
num n[999999];
int main()
{
    int x2,zhizhen = 0;cin>>x2;
    for(int i = 0;i < 999999;i++)
    {
        n[i].p = 0;n[i].a = 0;
    }
    while(1)
    {
        if(x2 == 1) break;
        for(int i = 2;i <= x2;i++)
            if(x2 % i == 0)
            {
                int flag = 0;
                for(int j = 0;j < zhizhen;j++)
                    if(n[j].p == i)
                        {n[j].a++;flag =1;break;}
                if(!flag) {n[zhizhen].p = i;n[zhizhen++].a = 1;}
                x2 /= i;break;
            }
    }
    for(int i = 0;i < zhizhen;i++) printf("%d  %d\n",n[i].p,n[i].a);
    return 0;
}

3.直接约分,每次约掉Xi和X2的最大公约数,约到X2=1时,就能证明E就是整数了,代码实现如下:

#include<stdio.h>
#include<string>
#include<sstream>
#include<string.h>
#include<iostream>
using namespace std;
int gcd(int a,int b)  //最大公约数
{
    return b == 0 ? a: gcd(b,a%b);
}
int main()
{
    string str;
    while(cin>>str)
    {
        int num[10005],x2;
        memset(num,0,sizeof(num));
        char temp;
        stringstream s(str);
        s>>num[0];
        s>>temp;
        s>>x2;
        int zhizhen = 1;
        while(s>>temp)
        {s>>num[zhizhen++];}
        for(int i = 0;i <zhizhen;i++)
        {
            x2 /= gcd(num[i],x2);
            if(x2 == 1) break;
        }
        if(x2 == 1) printf("YES\n");
        else printf("NO\n");

    }
    return 0;
}

2.Eratosthenes筛法:

①素数(质数):

一个大于1的自然数,除了1和它本身外,不能被其他自然数整除(除0以外)的数称之为素数(质数);否则称为合数。

不包含0、1,从2开始,其中1既不是质数,也不是和数

②素数定理:


π(x)表示不超过x的素数的个数,注意是约等于,只能说比较接近。

③程序实现:

#include<stdio.h>
#include<string>
#include<sstream>
#include<string.h>
#include<iostream>
using namespace std;
int main()
{
    int n,shu[99999];  //在机房的电脑上最多能开这么大的数组了
    cin>>n;
    memset(shu,1,sizeof(shu));
    shu[1] = 0;
    for(int i = 2;i <= n;i++)
        for(int j = 2;j * i <= n;j++)
            shu[i * j] = 0;
    for(int i = 1;i <= n;i++)
        if(shu[i]) printf("%d\n",i);
    return 0;
}
LRJ说这份代码可以改进,以下是改进的代码:

#include<stdio.h>
#include<string>
#include<sstream>
#include<math.h>
#include<string.h>
#include<iostream>
using namespace std;
int main()
{
//    freopen("out.txt","w",stdout);
    int n,shu[99999];
    cin>>n;
    int m = sqrt(n + 0.5);  //一个数<span lang="EN-US">n</span>如果是合数,那么它的所有的因子不超过<span lang="EN-US"> sqrt(n),因为</span>如果m能被2~m-1之间任一整数整除,其二个因子必定有一个小于或等于√m,另一个大于或等于√m。
    memset(shu,1,sizeof(shu));
    shu[1] = 0;
    for(int i = 2;i <= m;i++)  //i是因子
        if(shu[i])  //只有素数才判断,已经不是素数的其倍数肯定也已经不是了,真他妈好算法啊
        for(int j = i * i;j<= n;j += i)  //i*i之前的都已经判断完成
            shu[j] = 0;
    for(int i = 1;i <= n;i++)
        if(shu[i]) printf("%d\n",i);
    return 0;
}


3.拓展欧几里得算法:

①应用:线性方程ax+by=c ,已知a,b,c,求解x,y。

②思路:

ax+by=c有解 => c=k*gcd(a,b)=kd(因为d=gcd(a,b)=>d|(ax+by))

设 a>b。
⑴当 b=0,gcd(a,b)=a。此时 x=1,y=0;
⑵当ab != 0 时
设 ax+by=gcd(a,b);bx‘+(a mod b)y’=gcd(b,a mod b);
根据朴素的欧几里德原理有 gcd(a,b)=gcd(b,a mod b);
则:ax+by=bx‘+(a mod b)y’;
即:ax+by=bx‘+(a-[a/b]*b)y’=ay‘+bx’-[a/b]*by‘;
也就是ax+by==ay+b(x’-[a/b]*y);
根据系数相等得:x=y‘; y=x‘-[a/b]*y’;
这样我们就得到了求解 x,y 的方法:x,y 的值基于 x’,y‘.
上面的思想是以递归定义的,因为 gcd 不断的递归求解一定会有个时候 b=0,所以递归可以结束。

③由一组解生成剩余的解:

任取另外一组解(x2,y2),则ax1+by1=ax2+by2(都等于gcd(a,b)),变形得:a(x2-x1) = b(y2-y1)。假设gcd(a,b) = g,方程两边同除以g,得a‘(x2-x1) = b‘(y1-y2)。此时a‘和b‘互素,因此x2-x1一定是b‘的整数倍。设它为kb‘,计算得:y2-y1 = ka‘。

其任意解可以基于x1,y1  任意整数解可以写成(x1+kb‘,y1-ka‘),其中a‘=a/gcd(a,b)  b‘=b/gcd(a,b)。

若ax+by=g,(g=gcd(a,b),即g是a,b的最大公约数)有整数解(x1,y1);则ax+by=c(c是g的倍数)有整数解(cx1/g,cy1/g)

④代码实现

以下是LRJ的思路,表示没看懂。

#include<stdio.h>
#include<string>
#include<sstream>
#include<math.h>
#include<string.h>
#include<iostream>
using namespace std;
void gcd(int a,int b,int &d,int&x,int&y)
{
    if(!b){d = a;x = 1;y = 0;}
    else {gcd(b,a%b,d,y,x);y = y - x*(a/b);}
}
int main()
{
    int x,y,d;
    gcd(15,6,d,x,y);
    printf("%d %d\n",x,y);
    return 0;
}


4.同余与模算术:

①模算术公式:

(a+b)%n=(a%n+b%n)%n
(a-b)%n=((a%n)-(b%n)+n)%n  (由于a%n可能小于b%n,故需要在结果上加上n)
ab%n=((a%n)(b%n))%n

②同余:

定义:两个整数除以同一个整数,若得相同余数,则二整数同余。给定一个正整数n,如果两个整数a和b满足a-b能被n整除,即n|(a-b),那么就称整数a与b对模n同余
注意:当b=1时,ax=1(mod n)的解成为a关于模n的逆,它类似于实数运算中的“倒数”概念,要求ax-1=ny要有解,1必须是gcd(a,n)的倍数因此a和n必须互素,即gcd(a,n)=1,在满足这个条件的前提下,ax=1(mod n)只有唯一解。否则,方程无解!
性质:(ax-b)mod n=0即存在y,使:ax-b=ny成立。