数论
数论
一、整除
(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)\)
浙公网安备 33010602011771号