数论

数论

一、整除

(1)整除:

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

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

例1: 输入一个正整数n判断n是否能够被3和5整除,如果能够输出“Yes” ,否则输出“No”。\(n∈[1\) . . \(2^{31}-1]\)

#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\times q+r\)\(r\neq 0\),则 \(r\)\(a\%b\) 的余数 。并且 r 的取值范围为\([1\) . . \(b-1]\),这个范围内的数组成的余数域,称为剩余系。所有的数组成的域,称为完全剩余系

r = a % b;

余数的计算方法为 \(r=a-\lfloor\frac{a}{b}\rfloor\times b\) ,其中 \(\lfloor\frac{a}{b}\rfloor\) 表示 \(a\div b\) 的值向下取整。

r = a - a / b * b;

二、同余

同余概念: 若a,b为两个整数,且它们的差\(a-b\)能够被整数\(p\)整除 ,记做:\(p|(a-b)\),或者 \(a\%p=b\%p\) 则称为 \(a\)\(b\) 关于模 \(p\) 同余,记为:\(a\equiv b(mod \ p)\)。表示 \(a- b=p\times k \ (k\in Z)\)。 例如:\(32\equiv 2(mod\ 5)\)

同余性质:

1、对称性:若 \(a\equiv b(mod \ p)\),则 \(b\equiv a(mod \ p)\)

2、传递性:若 \(a\equiv b(mod \ p)\)\(b\equiv c(mod \ p)\),则 \(a\equiv c(mod \ p)\)

3、同加性:若 \(a\equiv b(mod \ p)\),则 \(a+c\equiv b+c(mod \ p)\)

4、同乘性:若\(a\equiv b(mod \ p)\)\(c\equiv d(mod \ p)\),则 \(a\times c\equiv b\times d(mod \ p)\)

5、同幂性:若\(a\equiv b(mod \ p)\),则 \(a^n\equiv b^n(mod \ p)\)

6、相关推论:

​ ①若 \(\textcolor{Red}{a\times b \ \% \ p = (a\%p\times b\%p)\ \%\ p}\)

​ ②若 \((a\pm b) \ \% \ p = (a\%p\pm b\%p)\ \%\ p\)

​ ③若 \(\textcolor{Red}{a^b \ \% \ p = (a\%p)^b\ \%\ p}\)

​ ④若 \(a\times c\equiv b\times c (mod \ p)\),则 \(b\equiv a(mod \ \frac{p}{gcd(c,p)}),c\neq0\)\(c\)\(p\) 互质 则 \(gcd(c,p)=1\)

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

​ ⑥若 \(a \% p=x\)\(a\%q=x\)\(p\)\(q\) 互质,则 \(a \ \%\ (p\times q)=x\)

例2:\(a^b\)取模 \(p\) 的值。\(a,b,p∈[ 1\). . \(10000]\)

算法思路: 直接求\(a^b\)次幂会导致数据溢出,例如\(3^{1000}\)已经远超出long long的取值范围,因此需要用到同余性质。

\(a^\textcolor{Red}{b}\%p=(a^\textcolor{Red}{b-1}\%p \times a\%p)\%p=((a^\textcolor{Red}{b-2}\%p \times a\%p)\%p \times a\%p)\%p=(((a^\textcolor{Red}{1}\%p \times a\%p)\%p\times a\%p)\%p…… \times 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 的因数是:\(1、2、3、4、6、9、12、18、36\)

例3: 输入一个正整数n,求出n的所有的因数,从小到大并以空格隔开。\(n∈[1\) . . \(2^{31}-1]\)

算法思路: 枚举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的因数\({1,36}{2,18}{3,12}{4,9}{6,6}\)所以按上面的方法枚举效率低

算法思路: 只需要枚举1到\(\sqrt{n}\),遇到能整除\(n\)的数\(i\)的同时,将\(n/i\)输出,但需要判断因数是否不同\(i\neq n/i\) 避免重复输出。算法时间复杂度为\(O(\sqrt{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\) . . \(2^{31}-1]\)

算法思路: 枚举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\) . . \(2^{31}-1]\)

算法思路: 从大到小枚举\(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( \log{n} )\)

#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\) . . \(2^{31}-1]\)

算法思路: \(lcm(a,b)\)表示a和b的最小公倍数,\(gcd(a,b)\)表示a和b的最大公因数。用公式 $lcm(a,b)=a\times b\div gcd(a,b) $ 。算法时间复杂度为\(O( \log{n} )\)

#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\) . . \(2^{31}-1]\)

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

#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)\),那么对于任意整数\(x\)\(y\),总有\(x\times a+y\times b\)\(d\) 的倍数。且对任何整数a、b和它们的最大公约数d,关于未知数x和y的线性于丢番图方程\(x\times a+y\times b=c\) 有整数解\((x,y)\)的充分必要条件是 \(c\)\(gcd(a,b)\) 的倍数。

证明方法:

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

\(s\)\(ax+by\) 最小正值,令\(q=\lfloor \frac{a}{s} \rfloor\) 则余数\(r=a \bmod s=a-q(ax+by)=a(1-qx)+b(-qy)\)

可见r也是\(a,b\)的线性组合,且r为a取模s的余数,可得\(0\leq r \lt s\)

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

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

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

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

算法思路:

\(gcd(a,b)=x\times a+y\times b = gcd(b,a\%b)=x_1\times b+y_1\times a\%b=x_1\times b+y_1\times(a-\lfloor\frac{a}{b}\rfloor\times b)=y_1\times a+(x_1-\lfloor\frac{a}{b}\rfloor\times y_1)\times b\)

化简\(x\times a+y\times b=y_1\times a+(x_1-\lfloor\frac{a}{b}\rfloor\times y_1)\times b\) $\rightarrow $ \(\textcolor{Red}{(x-y_1)\times a+(y-x_1+\lfloor\frac{a}{b}\rfloor\times y_1)\times b=0}\)

对于\(\forall{a\in Z,b\in Z}:(x-y_1)\times a+(y-x_1+\lfloor\frac{a}{b}\rfloor\times y_1)\times b=0\) ( \(\forall{a\in Z,b\in Z}\) 表示对任意的整数a和b满足,\(Z\) 表示整数集合 )

可得 \(x-y_1=0\)\(y-x_1+\lfloor\frac{a}{b}\rfloor\times y_1=0\)\(\begin{cases}x=y_1\\y=x_1-\lfloor\frac{a}{b}\rfloor\times y_1\end{cases}\)

根据\(\begin{cases}gcd(a,b)=a\times x+b\times y\\gcd(b,a\%b)=b\times x_1+a\%b\times y_1\end{cases}\),递归直到 \(a\%b=0\) 时有 \(gcd(b,a\%b)=b\times x_n\)\(gcd(b,a\%b)=b\)

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

#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\) . . \(2^{31}-1]\)

算法思路: ①判断n是否小于2。 ②枚举2到\(\sqrt{n}\)之间的所有整数,判断是否存在第三个因数。算法时间复杂度为 \(O( \sqrt{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\) . . \(2^{31}-1]\)

(a)朴素筛选法

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

#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(n \log{\log 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存入数组当中
            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(n\geq2)\)那么一定可以表示为若干个质数的乘积(唯一的形式),即:\(n=a_1\times a_2……\times a_k\)所有的\(a_i(1\leq i\leq k)\)都是n的质因数。

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

例10: 输入正整数n,输出n的所有的质因数并以空格隔开。\(n∈[2\) . . \(2^{31}-1]\)

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

#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为质数,则 \((p-1)! \equiv -1(mod\ p)\) 其中 \((p-1)!\) 表示 \((p-1)\) 的阶乘。

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

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

3、费马定理

\(p\)为质数,\(a\)为正整数,且\(a\)\(p\)互质,则:\(a^{p-1}\equiv1(mod\ p)\)

证明方法:

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

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

\(a\times 2a \times 3a……\times(p-1)a\equiv 1\times2\times3……\times p-1(mod\ p)\)

\(a^{p-1}\times(p-1)!\equiv(p-1)!\ (mod\ p)\)威尔逊定理得出 \((p-1)!\)\(p\) 互质,用同余性质推论得即得: \(a^{p-1}\equiv1(mod\ p)\)

posted @ 2025-02-12 14:20  KuaiZz  阅读(156)  评论(0)    收藏  举报