数论
目录:
1、素数
2、唯一分解定理
3、费马小定理
4、欧拉函数
5、欧几里得算法
(1)递归
(2)递推
6、扩展欧几里得
(1)求同余方程
(2)求乘法逆元
(3)求线性方程ax+by=c
7、快速幂
8、中国剩余定理
素数
(1)暴力求解法
根据素数的概念,没有1和其本身没有其他正因数的数。
所以只需枚举比这个数小的数,看能整除即可;
C++代码:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
bool determine(int number)
{
if(n<=2)return false;
if(!n%2)return false;
for(inti=3;i<=ceil(sqrt(number));i+=2)
//去掉了偶数的判断,效率提高一倍
/*如果number整除以i,那么会得到两个的因数,
而较小的那个因数不会超过number的二分之一次方;
所以只需判断到number的平方根向上取整即可; */
if(number%i);
else return false;
return true;
}
int main()
{
int sum;
cin>>sum;
if(determine(sum))
cout<<"YES!";
elsecout<<"NO!";
return 0;
}
时间复杂度:o(sqrt(n)/2);
空间复杂度:几乎没有;
(2)一般线性筛法:
因为任何一个合数都能分解成几个素数相乘的形式;
所以可以做一个表,首先把2设为质数,然后将2的倍数设为合数,剩下的数就是新得到的质数,然后重复这个过程,直到筛到合适的范围即可;
但是这个算法有缺陷:
1、 同一个数可能被筛多次,这就产生了多余的步骤。
2、 占用空间很大,如果使用bool数组的话,只能筛到1e9;
3、 从1-n筛,不能从m-n开始筛;
C++代码:
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
bool s[1000000000];
int m,n;
int main()
{
cin>>m>>n;
memset(s,true,n);
s[0]=s[1]=0;
//输出M—N之间所有素数;
for(inti=2;i<=ceil(sqrt(n));++i)
if(s[i])
{
for(intj=i;j<=n;++j)
if(s[i*j])
s[i*j]=false;
}
for(int i=m;i<=n;++i)
if(s[i])
cout<<i<<'';
return 0;
}
时间复杂度:o(n*loglogn);
空间复杂度:很大!注意数据大的话可能会爆空间;
(3)线性筛法求素数
这个占空间就更大了,需要使用一个bool数组和int数组
而亲身试验得到int数组最多开到1e8……
很无语,快确实是快了,但是测试数据一大,爆空间就更容易了;
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int m,n,sum;
bool inp[1000000000];
int s[100000000]={0,0};
int main()
{
cin>>m>>n;
for(int i=2;i<=n;++i)
{
if(!inp[i])
s[sum++]=i;
for(intj=0;j<sum&&i*s[j]<=n;++j)
{
inp[i*s[j]]=true;
if(!(i*s[j]))
break;
}
}
for(int i=m;i<=n;++i)
if(!inp[i])
cout<<i<<'';
return 0;
}
唯一分解定理
任何数都可以被唯一的分解成多个素数之积
例如:456=2*2*2*3*19;
C++代码:
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;
bool s[1000000];
int m,n,sum=0,num;
int Prime[1212121];
int zhi[1500];
void Primes()
{
for(int i=1;i<=num;++i)
s[i]=true;
s[0]=s[1]=0;
for(int i=2;i<=num;++i)
if(s[i])
{
Prime[++sum]=i;
for(intj=i;j<=num;++j)
if(s[i*j])
s[i*j]=false;
}
}
int main()
{
int flag=0;
cin>>num;
int number=num;
Primes();
if(s[num])
{
cout<<num<<'='<<num;
return 0;
}
cout<<num<<"=";str.chu();
while(num>1)
for(inti=1;num>1&&i<=sum;++i)
if(!(num%Prime[i]))
{
zhi[++flag]=Prime[i];
num/=Prime[i];
}
sort(zhi+1,zhi+flag+1);
cout<<zhi[1];
for(inti=2;i<=flag;++i)
cout<<"*"<<zhi[i];
return 0;
}
首先做一个质数表,并把质数存到数组里,然后用数模每个素数,如果为0则记录素数,最后排个序输出;
费马小定理
费马小定理:假如p是质数,且Gcd(a,p)=1,那么 a(p-1)≡1(mod p)。即:假如a是整数,p是质数,且a,p互质(即两者只有一个公约数1),那么a的(p-1)次方除以p的余数恒等于1。
利用这个性质可以判断数是否为素数
#include<cstdio>
#include<iostream>
usingnamespace std;
#defineLL long long
LL n,Prime[10]={2,3,5,7,11,13,17,19,23,29};
LL Mod(LLS,LL X)
{
LL Num=0;
while (S)
{
if (S&1)
Num=(Num+X)%n;
X=(X+X)%n;
S>>=1;
}
return Num;
}
LLCount(LL S,LL X)
{
LL Num=1;
while (S)
{
if (S&1)
Num=Mod(Num,X);
X=Mod(X,X);
S>>=1;
}
return Num;
}
boolCheck()
{
if (n==2)
return true;
else
if (n<2||!(n&1))
return false;
for (int a=0;a<10;a++)
{
if (n==Prime[a])
return true;
if (Count(n-1,Prime[a])!=1)
return false;
}
return true;
}
intmain()
{
scanf("%lld",&n);
if (Check())
cout<<"Yes";
else
cout<<"No";
return 0;
}
欧拉函数
欧拉函数φ(n)为不大于n的与n互素的数的个数;
A与B互素,表示a与b的最大公约数为1,即(a,b)=1;
欧拉函数的符号φ读作fhi,在搜狗的特殊符号里可以找到;
,其中pi为x的质因数,其中φ(1)=1(唯一与1互质的数是1本身)
设n为正整数,以 φ(n)表示不超过n且与n互素的正整数的个数,称为n的欧拉函数值
φ:N→N,n→φ(n)称为欧拉函数。
几个性质(来自百度百科)
1、若n是质数p的k次幂, ,因为除了p的倍数外,其他数都跟n互质。
2、欧拉函数是积性函数——若m,n互质,
3、特殊性质:当n为奇数时, , 证明与上述类似。
4、若n为质数则
5、设p是素数,a是一个正整数,那么
C++实现:
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;
bool s[1000000];
int m,n,sum=0,num;
int Prime[1212121];
int zhi[1500];
bool asd[1500];
int phi(int n)
{
int i,rea=n;
for(i=2;i*i<=n;i++)
{
if(n%i==0)
{
rea=rea-rea/i;
while(n%i==0) n/=i;
}
}
if(n>1)
rea=rea-rea/n;
return rea;
}
void Primes()
{
for(int i=1;i<=num;++i)
s[i]=true;
s[0]=s[1]=0;
for(int i=2;i<=num;++i)
if(s[i])
{
Prime[++sum]=i;
for(intj=i;j<=num;++j)
if(s[i*j])
s[i*j]=false;
}
}
int main()
{
int flag=0;
cin>>num;
int number=num;
Primes();
if(num==1||!num)
{
cout<<"fhi"<<'('<<number<<")=";
cout<<num;
return 0;
}
if(s[num])
{
cout<<"fhi"<<'('<<number<<")="<<number-1;
return 0;
}
while(num>1)
for(inti=1;num>1&&i<=sum;++i)
if(!(num%Prime[i]))
{
zhi[++flag]=Prime[i];
num/=Prime[i];
}
int fenzi=1,fenmu=1;
sort(zhi+1,zhi+flag+1);
for(inti=1;i<=flag;++i)
if(!asd[zhi[i]])
{
asd[zhi[i]]=true;
fenzi*=zhi[i]-1;
fenmu*=zhi[i];
}
cout<<"fhi("<<number<<")="<<number/fenmu*fenzi;
//cout<<"fhi("<<number<<")="<<fhi(number);
/*这是另一种求欧拉函数值的方法*/
return 0;
}
欧几里得算法
辗转相除法,根据公式(a,b)=(b,r)
其中r为a%b,即a/b;
C++代码:
(1)递归
#include<iostream>
#include<cstdio>
using namespace std;
int GCD(int a,int b)
{
if(a%b)
return GCD(b,a%b);
else return b;
}
int main()
{
int a,b;
cin>>a>>b;
cout<<GCD(a,b);
return 0;
}
(2)递推
#include<iostream>
using namespace std;
int main()
{
int a,b,r;
cin>>a>>b;
r=m%n;
while(r!=0)
{
a=b;
b=r;
r=m%n;
}
cout<<n;
return 0;
}
扩展欧几里得
扩展欧几里得又称斐蜀定理,对于不完全为 0 的非负整数 a,b,gcd(a,b)表示 a,b 的最大公约数,必然存在整数对 x,y ,使得 gcd(a,b)=ax+by;
求同余方程
#include<cstdio>
voidexgcb(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;y=0;
return;
}
int q=a/b;
int r=a%b;
exgcb(b,r,y,x);
y-=q*x;
}
intmain()
{
int x,y;
int a,b;
scanf("%d %d",&a,&b);
exgcb(a,b,x,y);
while(x<0)
x+=b;
printf("%d",x);
return 0;
}
求乘法逆元
#include<cstdio>
void exgcb(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;y=0;
return;
}
int q=a/b;
int r=a%b;
exgcb(b,r,y,x);
y-=q*x;
}
int Multiplicative inverse(int a,int b)
{
int x,y;
int gcb=GCD(a,b,x,y);
if(1%gcb)return -1;
x*=1%gcb;
b=abs(b);
int answer=x%b;
while(answer<=0)
answer+=b;
return answer;
}
int main()
{
int x,y;
int a,b;
scanf("%d%d",&a,&b);
exgcb(a,b,x,y);
while(x<0)
x+=b;
printf("%d\n",x);
cout<<Multiplicativeinverse(a,b);
return 0;
}
求线性方程ax+by=c
这个方程等同于ax≡c(mod b)
它有解的条件是(a,b)|c
方法一:脑子模拟,然后
#include<iostream>
using namespace std;
int main()
{
int a,b,c;
cin>>a>>b>>c;
int r=c%a;
c/=a;
int k;
b=0-b;
int asd=a-r;
c-=a;
int sdf=c/(0-b);
for(k=1;k<=sdf;++k)
cout<<c+b*k<<''<<a*k+asd<<endl;
}
输出所有解;
方法二:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int a,b,c,x,y;
int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;y=0;return a;
}
int e=exgcd(b,a%b,x,y);
int kkz=x;x=y;y=kkz-a/b*y;
return e;
}
int main()
{
scanf("%d%d%d",&a,&b,&c);
int k=exgcd(a,b,x,y);
if(c%k)printf("Impossible\n");
else
{
k=c/k;
x*=k;y*=k;
printf("x=%d,y=%d\n",x,y);
}
return 0;
}
快速幂
递归与非递归
#include<iostream>
using namespace std;
int quick(int,int,int);
int double_quick(int ,int ,int);
int main()
{
inta,b,c;//求a^b modc;
cin>>a>>b>>c;
cout<<double_quick(a,b,c);
cout<<endl<<quick(a,b,c);
}
int quick(int a,int b,int c)
{
intans=1;
if(b==1) return a;
ans=quick(a,b>>1,c)%c;
if(b&1) return ans*ans*a%c;
else return ans*ans%c;
}
int double_quick(int a,int b,int c)
{
int ans=1,base=a;
while(b!=0)
{
if(b&1!=0)
ans*=base%c;
base*=base%c;
b>>=1;
}
return ans%c;
}
中国剩余定理(孙子定理)
#include<iostream>
using namespace std;
void exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;y=0;
return;
}
int q=a/b;
int r=a%b;
exgcd(b,r,y,x);
y-=q*x;
}
int CRT(int a[],int m[],int n)
{
int M = 1;
int ans = 0;
for(int i=1; i<=n; i++)
M *= m[i];
for(int i=1; i<=n; i++)
{
int x, y;
int Mi = M / m[i];
exgcd(Mi,m[i],x,y);
ans = (ans + Mi * x *a[i]) % M;
}
if(ans < 0) ans += M;
return ans;
}
int main()
{
int n,mi;
int a[5555],m[5555];
cin>>n;
mi=n;
while(n--)
cin>>a[n]>>m[n];
cout<<CRT(a,m,mi);
}