数论

数论

一、整除

(1)整除:

整除概念:a 是整数,b 是非零整数。若存在一个整数 q,使得 a=b×q+rr=0 , 即说明 a 能被 b 整除,记做 b|a,且称 ab倍数ba因数。例如:9%3==0 记做 3|9

if(a%b==0) cout<<"b|a";

例1: 输入一个正整数n判断n是否能够被3和5整除,如果能够输出“Yes” ,否则输出“No”。n[1 . . 2311]

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n;
    cin>>n;
    if(n%3==0&&n%5==0)
        cout<<"Yes";
    else
        cout<<"No";
    return 0;
}

(2)余数:

余数概念:a 是整数,b 是非零整数。若存在一个整数 q,使得 a=b×q+rr0,则 ra%b 的余数 。并且 r 的取值范围为[1 . . b1],这个范围内的数组成的余数域,称为剩余系。所有的数组成的域,称为完全剩余系

r = a % b;

余数的计算方法为 r=aab×b ,其中 ab 表示 a÷b 的值向下取整。

r = a - a / b * b;

二、同余

同余概念: 若a,b为两个整数,且它们的差ab能够被整数p整除 ,记做:p|(ab),或者 a%p=b%p 则称为 ab 关于模 p 同余,记为:ab(mod p)。表示 ab=p×k (kZ)。 例如:322(mod 5)

同余性质:

1、对称性:若 ab(mod p),则 ba(mod p)

2、传递性:若 ab(mod p)bc(mod p),则 ac(mod p)

3、同加性:若 ab(mod p),则 a+cb+c(mod p)

4、同乘性:若ab(mod p)cd(mod p),则 a×cb×d(mod p)

5、同幂性:若ab(mod p),则 anbn(mod p)

6、相关推论:

​ ①若 a×b % p=(a%p×b%p) % p

​ ②若 (a±b) % p=(a%p±b%p) % p

​ ③若 ab % p=(a%p)b % p

​ ④若 a×cb×c(mod p),则 ba(mod pgcd(c,p)),c0cp 互质 则 gcd(c,p)=1

​ ⑤除法不满足同除性,具体详见 逆元

​ ⑥若 a%p=xa%q=xpq 互质,则 a % (p×q)=x

例2:ab取模 p 的值。a,b,p[1. . 10000]

算法思路: 直接求ab次幂会导致数据溢出,例如31000已经远超出long long的取值范围,因此需要用到同余性质。

ab%p=(ab1%p×a%p)%p=((ab2%p×a%p)%p×a%p)%p=(((a1%p×a%p)%p×a%p)%p×a%p)%p

#include<bits/stdc++.h>
using namespace std;
int main(){
    int a,b,p,s=1;
    cin>>a>>b>>p;
    a=a%p;
    //利用a^b%p = (a%p)^b%p减少运算规模,这句不写也不会错,注意数据溢出。
    for(int i=1;i<=b;i++)
		s=s*a%p;
    //利用s*a%p=(s%p*a%p)%p,这里因为s和a都小于p故 (s%p*a%p)%p=s*a%p
    cout<<s;
    return 0;
}

当然还有更高效的快速幂算法,自行百度,本资料不做介绍。

三、因数

(1)因数:

因数概念: 如果a÷b余数为0,就称b为a的因数或约数。比如说正整数 36 的因数是:123469121836

例3: 输入一个正整数n,求出n的所有的因数,从小到大并以空格隔开。n[1 . . 2311]

算法思路: 枚举1到n之间(包括1和n)所有数,将能整除n的数输出。算法时间复杂度为O(n)

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        if(n%i==0)
            cout<<i<<" ";
    return 0;
}

算法评估: 因数都是成对出现的, 比如说36的因数1362183124966所以按上面的方法枚举效率低

算法思路: 只需要枚举1到n,遇到能整除n的数i的同时,将n/i输出,但需要判断因数是否不同in/i 避免重复输出。算法时间复杂度为O(n)

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n;
    cin>>n;
    //不使用i<=sqrt(n)这样每循环一次都会计算一次根号n,导致效率降低
    for(int i=1;i*i<=n;i++)					
        if(n%i==0){
			cout<<i<<" ";
            if(i!=n/i)	cout<<n/i<<" ";	
            //⭐判断判断两个因数是否相同,不同则输出
        }
    return 0;
}

(2)公因数:

公因数概念: 对于两个正整数a和b,如果c能够同时整除a和b ,则c是a和b的公因数。

例4: 输入两个正整数a和b,求出a和b的所有公因数,从小到大并以空格隔开。a,b[1 . . 2311]

算法思路: 枚举1到 min(a , b),如果能同时整除 a和b的数就是a和b的公因数。 min(a,b)表示 a和b的最小值。算法时间复杂度为O(n)

#include<bits/stdc++.h>
using namespace std;
int main(){
	int a,b;
    cin>>a>>b;
    int n=min(a,b);				//n为a和b的最小值
    for(int i=1;i<=n;i++)		//i<=a&&i<=b
        if(a%i==0 && b%i==0)
            cout<<i<<" ";
    return 0;
}

(3)最大公因数 GCD (Greatest Common Divisor):

最大公因数概念: a和b的所有公因数中最大的数。又叫做最大公约数。

例5: 输入两个正整数a和b,求出a和b的最大公因数。a,b[1 . . 2311]

算法思路: 从大到小枚举min(a,b)到1,第一个能够同时整除a和b的数,即为最大公因数。算法时间复杂度为O(n)

#include<bits/stdc++.h>
using namespace std;
int main(){
	int a,b;
    cin>>a>>b;
    int m=min(a,b);				//n为a和b的最小值
    for(int i=m;i>=1;i--)		//从大到小枚举,直到遇到第一个能够整除a和b的数
        if(a%i==0&&b%i==0){
            cout<<i;			
            break;				//这里也可以使用return 0;
        }
    return 0;
}

算法评估: 对于上面这个程序,他的运行效率低,如果a和b很大的话,程序执行时间会随着a和b增加而成线性增加。故引入欧几里得算法(辗转相除法)。

算法思路: gcd(a,b)表示a和b的最大公因数,则有 gcd(a,b)=gcd(b,a%b)。算法时间复杂度为O(logn)

#include<bits/stdc++.h>
using namespace std;
int main(){
    int a,b,r;
    cin>>a>>b;
    do{
        r=a%b;
        a=b;
        b=r;
    }while(r);
    cout<<a;
    return 0;
}

递归实现:

#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b){
    return b==0 ? a : gcd(b,a%b);
}
int main(){
    int a,b;
    cin>>a>>b;
    cout<<gcd(a,b);
    return 0;
}

(4)最小公倍数LCM (Lowest Common Multiple):

最小公倍数概念: a和b的所有公共倍数中最小的数。

例6: 输入两个正整数a和b,求出a和b的最小公因数。a,b[1 . . 2311]

算法思路: lcm(a,b)表示a和b的最小公倍数,gcd(a,b)表示a和b的最大公因数。用公式 lcm(a,b)=a×b÷gcd(a,b) 。算法时间复杂度为O(logn)

#include<bits/stdc++.h>
#define  ll long long
using namespace std;
int main(){
    //⭐由于a和b的乘积会超过int,用long long处理数据溢出
    ll a,b,r;			
    cin>>a>>b;
    //记录a和b的乘积,因为a和b的值会在辗转相除的过程中改变
    ll times=a*b;		
    do{
        r=a%b;
        a=b;
        b=r;
    }while(r);
    //a为最大公因数,用公式lcm(a,b)=a*b/gcd(a,b)求出最小公倍数
    ll lcm=times/a;		
    cout<<lcm;
    return 0;
}

(5)互质:

互质概念: 有正整数a和b,若a和b的最大公因数为1,则称为a和b互质。

例7: 输入两个正整数a和b,求判断a和b是否互质,如果互质输出“Coprime”,否则输出“Not Coprime”。a,b[1 . . 2311]

算法思路: 利用欧几里得算法计算出a和b的最大公因数,判断若为1,则a和b互质。算法时间复杂度为O(logn)

#include<bits/stdc++.h>
using namespace std;
int main(){
    int a,b,r;
    cin>>a>>b;
    do{
        r=a%b;
        a=b;
        b=r;
    }while(r);
    if(a==1)	cout<<"Coprime";
    else		cout<<"Not Coprime";
    return 0;
}

(6)裴蜀定理:

裴蜀定理概念: 若a,b是整数,且记d=gcd(a,b),那么对于任意整数xy,总有x×a+y×bd 的倍数。且对任何整数a、b和它们的最大公约数d,关于未知数x和y的线性于丢番图方程x×a+y×b=c 有整数解(x,y)的充分必要条件是 cgcd(a,b) 的倍数。

证明方法:

d=gcd(ab),则 d|ad|b ,由于整除的性质有 $ \forall x,y\in Zd|(ax+by)$

sax+by 最小正值,令q=as 则余数r=amods=aq(ax+by)=a(1qx)+b(qy)

可见r也是a,b的线性组合,且r为a取模s的余数,可得0r<s

又因为s是ax+by的最小值,故余数r=0 ,故可得 s|as|b ,s为a和b的公约数,可得 ds ---- ①

又因为 d|ad|b ,且 s 是a,b的线性组合,可得 d|ss>0ds ---- ② 故 d=s ,则命题成立。

(7)扩展欧几里得算法 *

扩展欧几里得算法概念: 该算法是用来求已知两个正整数(a,b)时,求一组整数解(x,y),使得x×a+y×b=gcd(a,b),根据裴蜀定理该组解一定存在。且 xy 中一个很可能为负数。扩展欧几里得算法可以用于求模反元素(又称模逆元),而模逆元在RSA加密算法中非常重要。

算法思路:

gcd(a,b)=x×a+y×b=gcd(b,a%b)=x1×b+y1×a%b=x1×b+y1×(aab×b)=y1×a+(x1ab×y1)×b

化简x×a+y×b=y1×a+(x1ab×y1)×b (xy1)×a+(yx1+ab×y1)×b=0

对于aZbZ:(xy1)×a+(yx1+ab×y1)×b=0 ( aZbZ 表示对任意的整数a和b满足,Z 表示整数集合 )

可得 xy1=0yx1+ab×y1=0{x=y1y=x1ab×y1

根据{gcd(a,b)=a×x+b×ygcd(b,a%b)=b×x1+a%b×y1,递归直到 a%b=0 时有 gcd(b,a%b)=b×xngcd(b,a%b)=b

可得 xn=1,再令 yn=0 , 在通过{xn,yn}的解{1,0}递推得到原方程的一组解{x,y}。算法时间复杂度为O(logn)

#include<bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y){
    int gcd,tmp;
    if(b==0){
        x=1;
        y=0;
        return a;
	}
    gcd=exgcd(b,a%b,x,y);
    tmp=x;
    x=y;
    y=tmp-a/b*y;
    return gcd;
}
int main(){
    int a,b,x,y,gcd;
    cin>>a>>b;
    gcd=exgcd(a,b,x,y);
    cout<<x<<" "<<y<<endl;
    cout<<gcd;
    return 0;
}

四、质数

(1)判断质数

质数概念: ①在大于1的数当中 。②因数只有1和它本身 即为素数也称作质数。

例8: 输入正整数n,判断n是否是质数,如果是质数输出“Prime”,否则输出“Not Prime”。n[1 . . 2311]

算法思路: ①判断n是否小于2。 ②枚举2到n之间的所有整数,判断是否存在第三个因数。算法时间复杂度为 O(n)

#include<bits/stdc++.h>
using namespace std;
/*自定义函数isPrime用于实现判断素数的功能,如果是素数函数返回1,不是则返回0*/
int isPrime(int n){				
    if(n<2) return 0;			//如果n小于2 函数返回0
    for(int i=2;i*i<=n;i++)		//从2开始枚举到
        if(n%i==0)
            return 0;			//如果n存在第三个因数 函数返回0
    return 1;					//如果n大于1并且不存在除了1和本身以外的第三个因数 函数返回1
}
int main(){
    int n;
    cin>>n;
    if( isPrime(n) )			//将n的值代入函数isPrime,用于判断n是否是素数
        cout<<"Prime";
    else
        cout<<"Not Prime";
    return 0;
}

(2)筛选范围内的质数

例9: 输入正整数n,输出1到n之间所有的质数,用空格隔开。n[2 . . 2311]

(a)朴素筛选法

算法思路: 枚举2至n之间的所有数,用判断质数的函数依次判断。算法时间复杂度O(nn)

#include<bits/stdc++.h>
using namespace std;
int isPrime(int n){				
    if(n<2) return 0;			//如果n小于2 函数返回0
    for(int i=2;i*i<=n;i++)		//从2开始枚举到根号n
        if(n%i==0)
            return 0;			//如果n存在第三个因数 函数返回0
    return 1;					//n大于1 且 因数只有1和本身 函数返回1
}
int main(){
    int n;
    cin>>n;
    for(int i=2;i<=n;i++)
        if(isPrime(i))			//判断i是否是质数
            cout<<i<<" ";
    return 0;
}

(b)埃式筛选法

算法思路: 枚举2到n之间的所有数,每次枚举时去掉质数的倍数,算法时间复杂度O(nloglogn),在较小数据范围内可近似为线性阶O(n)

#include<bits/stdc++.h>
using namespace std;
int isPrime[1000];				//数组isPrime[i]记录i是否为质数,是为0,不是为1
int priList[1000];				//数组priList[k]记录2到n之间第k个质数
int main(){
    int n,k=0;
    cin>>n;
    for(int i=2;i<=n;i++)
        if(isPrime[i]==0){		//判断是否为质数
			priList[++k]=i;		//将质数i存入数组当中
            for(int j=2*i;j<=n;j+=i)
                isPrime[j]=1;	//将质数i的倍数标记为非质数
        }
    for(int i=1;i<=k;i++)
        cout<<priList[i]<<" ";
    return 0;
}

(c)欧拉筛选法(线性筛选法)

算法思路: 对于埃式筛选法,影响效率的最大问题就是重复,比如合数24分别被2、3、4、6处理过,存在重复处理,为了提高效率只需要处理一次即可。故如果该数是合数的话,只需要让该数被它的最小的质因数处理即可,例如合数24只需要被2处理,同理30只需要被2处理,而不再需要被5处理。虽然看代码上不是一个O(n)的算法,但实际处理时算法时间复杂度接近O(n)

#include<bits/stdc++.h>
using namespace std;
int isPrime[1000];				//数组isPrime[i]记录i是否为质数,是为0,不是为1
int priList[1000];				//数组priList[k]记录2到n之间第k个质数
int main(){
    int n,k=0;
    cin>>n;
    for(int i=2;i<=n;i++){
        if(isPrime[i]==0)		//将素数存入质数表
            priList[++k]=i;
        //枚举素数表中的数,将i的priList[j]倍数标记为非质数
    	for(int j=1;j<=k && i*priList[j]<=n;j++){
            isPrime[ i*priList[j] ]=1;
            //如果当前这个数i的因数包含质数priList[j]即停止筛选。
            if(i%priList[j]==0)
                break;
        }
    }
	for(int i=1;i<=k;i++)
        cout<<priList[i]<<" ";
    return 0;
}

(3)质数相关定理

1、唯一分解定理

若整数n(n2)那么一定可以表示为若干个质数的乘积(唯一的形式),即:n=a1×a2×ak所有的ai(1ik)都是n的质因数。

质因数概念: 能整除给定正整数的质数。

例10: 输入正整数n,输出n的所有的质因数并以空格隔开。n[2 . . 2311]

算法思路: 先从最小的质数2开始分解,直到不能能分解的时候选择下一个质数3,以此类推,直到将该数n分解为1为止。若n无法被分解,说明n现在已经是素数。例如: | 2042,102 | 1022,51 | 513,17 | 最终 n=17 为质数无法拆分直接输出。算法时间复杂度为O(logn)

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n;
    cin>>n;
    //枚举2到根号n之间的数,如果能整除即为质因数
    for(int i=2;i*i<=n;i++)
        while(n%i==0){
            cout<<i<<" ";
            n/=i;
        }
    //如果最终n不为1,则说明当前n为质数。
   	if(n!=1)
        cout<<n;
    return 0;
}

2、威尔逊定理

若p为质数,则 (p1)!1(mod p) 其中 (p1)! 表示 (p1) 的阶乘。

同理,若某正整数 p,有 (p1)!1(mod p),则 p 一定是质数。

利用 p|(p1)!+1 和 sin函数的特点,可以构造函数 f(n)=sin(π×((n1)!+1)/n),这个函数的与x轴相交的点都是素数所在的点。

3、费马定理

p为质数,a为正整数,且ap互质,则:ap11(mod p)

证明方法:

p1 个整数 a,2a,3a,(p1)a 这些数一定都不是p的倍数,并且没有任何两个数同于取模 p 的。

② 因此对于这 p1 个整数 a,2a,3a,(p1)a对于 p 的余数,即不为0,又不两两同余,因此对于这 p1 个数取模 p 后得到的余数一定是完全剩余系,即一定是 1,2,3,p1 的某种排列情况,即:

a×2a×3a×(p1)a1×2×3×p1(mod p)

ap1×(p1)!(p1)! (mod p)威尔逊定理得出 (p1)!p 互质,用同余性质推论得即得: ap11(mod p)

posted @   KuaiZz  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示