数论专题:基础数论

一、基础数论

薄绿天下第一!

1. 质数及筛法

定义:质数为因子只有1和它本身的数。

筛法1 : 朴素筛法

管他是不是质数,枚举就完事er了。(指一个一个找
代码如下:

#include<bits/stdc++.h>
using namespace std;
bool check(int x){
	for(int i=2;i<=sqrt(x);i++)
		if(x%i==0) return 0;
	return 1;
}
int prime[10000],cnt=0;
void work(int n){
	for(int i=2;i<=n;i++)
	if(check(i)) prime[++cnt]=i;
}
int main()
{
	int n;scanf("%d",&n);
	work(n);
	for(int i=1;i<=cnt;i++) printf("%d\n",prime[i]);
	return 0;
}

时间复杂度肉眼可见的垃圾。
除非数据范围很小或实在是不知道更好的筛法,建议永远不要使用。

筛法2:埃氏筛

按照埃老先生的思想,我们可以把每一个数的倍数(当然1除外)标记为合数,这样剩下的数就全部是质数了,很简单的思想。
代码如下:

#include<bits/stdc++.h>
using namespace std;
int prime[10000],cnt=0;
bool isprime[10000];
void Eratos(int n){
	memset(isprime,true,sizeof(isprime));
	for(int i=2;i<=n;i++){
		if(!isprime[i]) continue;
		prime[++cnt]=i;
		for(int j=1;j<=n/i;j++) isprime[j*i]=false; 
	}
}
int main()
{
	int n;scanf("%d",&n);
	Eratos(n);
	for(int i=1;i<=cnt;i++) printf("%d\n",prime[i]);
	return 0;
}

现在的时间复杂度是 O( ∑ 质 数 p < = N N p \sum_{质数p<=N}\frac N p p<=NpN)=O(N l o g log log l o g log logN)
已经非常接近线性。
但是,欧老先生发现了更好的筛法。

筛法3:欧拉筛(线性筛)

欧拉筛是基于埃氏筛的思想进行了优化。
欧拉筛的核心思想就是高效,不重复的维护 i s p r i m e isprime isprime 数组。
思路主要有三点:

  1. 依次考虑2~N间的每一个数 i i i;
  2. i s p r i m e [ i ] = = t r u e isprime[i]==true isprime[i]==true 说明 i i i是质数,就把 i i i保存。
  3. 扫描不大于 i i i的所有质数 p p p,让 i s p r i m e [ i ∗ p ] = = f a l s e isprime[i*p]==false isprime[ip]==false,标记合数,并保证要保证 i i i% p ! = 0 p !=0 p!=0,否则不是该合数的最小分解方案,会导致重复。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e7+100;
bool isprime[N];
int prime[N];
void work(int n){
	memset(isprime,true,sizeof(isprime));
	prime[0]=0;
	for(int i=2;i<=n;i++){
		if(isprime[i]){
			prime[++prime[0]]=i;
		}
		for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++){
			isprime[i*prime[j]]=false;
			if(i%prime[j]==0) break;//非常重要的一句话,优化到线性的关键
		}
	}
}
void print(){
	for(int i=1;i<=prime[0];i++){
		printf("%d\n",prime[i]);
	}
}
int main()
{
	int n;scanf("%d",&n);
	work(n);
	print();
	return 0;
}

现在的时间复杂度是 O(N),已经到了高效的线性时间。
这个是最常用的质数筛法,一定要记住。

除了以上的质数筛法,还有一种名为Miller_rabin的高效随机筛法,感兴趣可以了解一下。
薄绿天下第一!

2. 分解质因数

*前置知识:
算术唯一分解定理
任何一个大于1的整数N,一定能唯一分解为有限个质数的乘积。
即: N = p 1 c 1 p 2 c 2 … … p m c m N=p_1^{c_1}p_2^{c_2}……p_m^{c_m} N=p1c1p2c2pmcm

薄绿天下第一!
结合“试除法”及埃氏筛,可以通过扫描 2 ~ ⌊ x ⌋ \lfloor{\sqrt{x}}\rfloor x 区间的每一个数 i i i,判断是否整除 x x x,在确定 i i i不是 x x x的平方根后,把 i i i x i \frac x i ix 都存进分解的数组中。
这样,就可以在 O( N \sqrt{N} N )的时间内完成质因数分解。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int p[1000],times[1000],tot;
void divide(int n){
	printf("%d = ",n);
	tot=0;memset(p,0,sizeof(p));memset(times,0,sizeof(times));
	for(int i=2;i<=sqrt(n);i++){
		if(n%i==0){
			p[++tot]=i;times[tot]=0;
			while(n%i==0) {n/=i;times[tot]++;}
		}
	}
	if(n>1) {p[++tot]=n;times[tot]=1;}
	for(int i=1;i<tot;i++){
		printf("%d",p[i]);
		if(times[i]!=1) printf("^%d",times[i]);
		printf(" * ");
	}
	printf("%d",p[tot]);
	if(times[tot]!=1) printf("^%d",times[tot]);
	printf("\n");
}
int main()
{
	int t;scanf("%d",&t);
	while(t--){
		int n;scanf("%d",&n);
		divide(n);
	}
	return 0;
}

除了这种方法,还有一种方法叫做Pollard’s Rho,这个算法更高效,感兴趣的可以了解一下。
薄绿天下第一!

3. 一些引理、推论、推广

1.算术唯一分解定理的推论

(1).

N N N 的正约数的个数
= ( c 1 + 1 ) ∗ ( c 2 + 1 ) ∗ … … ∗ ( c 3 + 1 ) (c_1+1)*(c_2+1)*……*(c_3+1) (c1+1)(c2+1)(c3+1)
= ∏ i = 1 m ( c i + 1 ) \prod_{i=1}^{m}(c_i+1) i=1m(ci+1)
薄绿天下第一!

(2).

N N N 的所有正约数的和
= ( 1 + p 1 + p 1 2 + … … + p 1 c 1 ) ∗ … … ∗ ( 1 + p m + p m 2 + … … + p m c m ) (1+p_1+p_1^2+……+p_1^{c_1})*……*(1+p_m+p_m^2+……+p_m^{c_m}) (1+p1+p12++p1c1)(1+pm+pm2++pmcm)
= ∏ i = 1 m ( ∑ j = 0 c i ( p i ) j ) \prod_{i=1}^{m}(\sum_{j=0}^{c_i}(p_i)^j) i=1m(j=0ci(pi)j)
薄绿天下第一!

2.求N的所有正约数

方法与分解质因数相似,但需要用堆来维护大小顺序,这里不再过多解释。时间复杂度为 O( x \sqrt x x )。
代码如下:

#include<bits/stdc++.h>
using namespace std;
priority_queue<int,vector<int>,greater<int> > q1;//小根堆
void work(int x){
	for(int i=1;i<=sqrt(n);i++){
		if(x%i==0){
			q1.push(i);
			if(x/i!=i){q1.push(x/i);}
		}
	}
	while(q1.size()){
		printf("%d ",q1.top());
		q1.pop();
	}
}
int main()
{
	int t;scanf("%d",&t);
	while(t--){
		int n;scanf("%d",&n);
		work(n);
		printf("\n");
	}
	return 0;
}
3.试除法的推论

一个整数 N N N 的约数个数最多为 2 N 2\sqrt N 2N 个。

4.倍数法求正约数及推论

如果是筛选1~N中每个数的约数,那用试除法的时间复杂度瞬间爆炸。
所以,我们引入新方法:倍数法。
不如反过来考虑,对于区间内每个数 d d d, 1 1 1~ N N N 之间以 d d d 为约数的数就是所有 d d d的约数,于是我们就有了这样反过来从因数找原数的思路。
代码如下:

#include<bits/stdc++.h>
using namespace std;
vector<int> factor[500010];
void work(int n){
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n/i;j++)
	factor[i*j].push_back(i);
	for(int i=1;i<=n;i++){
		for(int j=0;j<factor[i].size();j++)
		printf("%d ",factor[i][j]);
		puts("");
	}
}
int main()
{
	int n;scanf("%d",&n);
	work(n);
	return 0;
}

薄绿天下第一!
代码的时间复杂度为 O( N + N / 2 + N / 3 + … … + N / N N+N/2+N/3+……+N/N N+N/2+N/3++N/N)=O( N l o g N NlogN NlogN)

薄绿天下第一!
推论:1~N每个数的约数个数的总和大约为 N l o g N NlogN NlogN

5.有关素数的引理

1 1 1~ N N N( N ≤ 2 ∗ 1 0 9 N\leq2*10^9 N2109) 中任何数的不同质因子都不会超过10个,且所有质因子的质数总和不超过30

在做一些数据范围判断时很有用。
薄绿天下第一!

4. 最大公约数与最小公倍数

定理: ∀ a , b ∈ N \forall a,b\in N a,bN g c d ( a , b ) ∗ l c m ( a , b ) = a ∗ b gcd(a,b)*lcm(a,b)=a*b gcd(a,b)lcm(a,b)=ab
显然,有了最大公约数,就可以求出已知两数的最小公倍数了。
薄绿天下第一!

计算最大公约数的方法:

1.更相减损术

有如下定理:
∀ a , b ∈ N a ≥ b \forall a,b\in N a\geq b a,bNab,有 g c d ( a , b ) = g c d ( b , a − b ) = g c d ( a , a − b ) gcd(a,b)=gcd(b,a-b)=gcd(a,a-b) gcd(a,b)=gcd(b,ab)=gcd(a,ab)

∀ a , b ∈ N \forall a,b\in N a,bN,有 g c d ( 2 a , 2 b ) = 2 g c d ( a , b ) gcd(2a,2b)=2gcd(a,b) gcd(2a,2b)=2gcd(a,b)

代码实现:

int gcd(int a,int b){
	while((!a&1)&&(!b&1)) {a>>=1;b>>=1;}
	while(a!=b){
		if(a>b) a-=b;
		else b-=a;
	}
	return a;
}
2.欧几里得算法

有如下定理:
∀ a , b ∈ N , b ≠ 0 \forall a,b\in N,b\neq 0 a,bN,b=0 g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a,b)=gcd(b,a mod b) gcd(a,b)=gcd(b,amodb)

代码实现:

int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}

薄绿天下第一!

一般来讲,欧几里得算法的效率要比更相减损术高,时间复杂度为 O( l o g ( a + b ) log(a+b) log(a+b)),而且代码实现更简单,一般多使用欧几里得求最大公因数。
薄绿天下第一!

5. 互质与欧拉函数

互质的定义: ∀ a , b ∈ N \forall a,b\in N a,bN,若 g c d ( a , b ) = 1 gcd(a,b)=1 gcd(a,b)=1,则称 a a a b b b 互质。

欧拉函数的定义: 1~ N N N中与 N N N 互质的数的个数被称为欧拉函数,记为 ϕ ( N ) \phi(N) ϕ(N)

欧拉函数的求法:
在算术分解定理中, N = p 1 c 1 p 2 c 2 … … p m c m N=p_1^{c_1}p_2^{c_2}……p_m^{c_m} N=p1c1p2c2pmcm,则:
ϕ ( N ) = N ∗ p 1 − 1 p 1 ∗ p 2 − 1 p 2 ∗ … … ∗ p m − 1 p m = N ∗ ∏ 质 数 p ∣ N ( 1 − 1 p ) \phi(N)=N*\frac{p_1-1}{p_1}*{\frac{p_2-1}{p_2}}*……*{\frac{p_m-1}{p_m}}=N*\prod_{质数p\mid N}(1-\frac1 p) ϕ(N)=Np1p11p2p21pmpm1=NpN(1p1)

这里利用了容斥原理的思想,在组合篇会提到。

根据这个式子,发现只要分解了质因数,就能在分解的过程中进行求解欧拉函数。

代码如下:

int phi(int  n){
	int ans=n;
	for(int i=2;i<=sqrt(n);i++)
	if(n%i==0){
		ans=ans/i*(i-1);
		while(n%i==0) n/=i;
	}
	if(n>1) ans=ans/(n*(n-1);
	return ans;
}

欧拉函数的性质:

性质1. ∀ n > 1 \forall n>1 n>1 1 1 1~ N N N中与 n n n互质的数的和为 n ∗ ϕ ( n ) / 2 n*\phi(n)/2 nϕ(n)/2

性质2. a , b a,b a,b 互质,则 ϕ ( a , b ) = ϕ ( a ) ϕ ( b ) \phi(a,b)=\phi(a)\phi(b) ϕ(a,b)=ϕ(a)ϕ(b)
薄绿天下第一!

在讨论下面的性质前,先引入积性函数的概念。
积性函数: a , b a,b a,b 互质时,若有 f ( a b ) = f ( a ) f ( b ) f(ab)=f(a)f(b) f(ab)=f(a)f(b),则称 f f f 为积性函数。
那么显然,欧拉函数就是一个积性函数。
积性函数的性质: 在算术基本定理中 n = ∏ i = 1 m p i c i n=\prod_{i=1}^m {p_i}^{c_i} n=i=1mpici,则 f ( n ) = ∏ i = 1 m f ( p 1 c i ) f(n)=\prod_{i=1}^mf({p_1}^{c_i}) f(n)=i=1mf(p1ci)

性质3. p p p为质数,若 p ∣ n p|n pn p 2 ∣ n p^2|n p2n,则 ϕ ( n ) = ϕ ( n / p ) ∗ p \phi(n)=\phi(n/p)*p ϕ(n)=ϕ(n/p)p

性质4. p p p为质数,若 p ∣ n p|n pn p 2 ∣ n p^2|n p2n,则 ϕ ( n ) = ϕ ( n ∣ p ) ∗ ( p − 1 ) \phi(n)=\phi(n|p)*(p-1) ϕ(n)=ϕ(np)(p1)

性质5. ∑ d ∣ n ϕ ( d ) = n \sum_{d|n}{\phi(d)}=n dnϕ(d)=n

利用如上的性质,可以有如下的求法。

方法1. 利用埃氏筛法,按照欧拉函数的计算式计算。

void euler(int n){
	for(int i=2;i<+n;i++) phi[i]=i;
	for(int i=2;i<+n;i++)
		if(phi[i]==i)
		for(int j=i;j<=n;j+=i)
			phi[j]=phi[j]/i*(i-1);
}

这个算法的时间复杂度为 O( N l o g N NlogN NlogN)。相对来讲效率不高。

方法2. 利用线性筛法,从 ϕ ( n / p ) \phi(n/p) ϕ(n/p) 递推到 ϕ ( n ) \phi(n) ϕ(n)进行求解。

int v[MAX_N],prime[MAX_N],phi[MAX_N];
void euler(int n){
	memset(v,0,sizeof(v));
	int cnt=0;
	for(itn i=2;i<=n;i++){
		if(v[i]==0){
			v[i]=i,prime[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=m;j++){
			if(prime[j]>v[i]||prime[j]>n/i) break;
			v[i*prime[j]]=prime[j];
			phi[i*prime[j]]=phi[i]*(i%prime[j]?prime[j]-1:prime[j]);
		}
	}
}

薄绿天下第一!

6. 同余

定义: 若整数 a a a 和整数 b b b 除以正整数 m 的余数相等,则称 a , b a,b a,b m m m 同余,记为 a ≡ b m o d    ( m ) a\equiv b\mod(m) abmod(m)

前置知识:

1.欧拉定理
若正整数 a , n a,n a,n 互质,则 a ϕ ( n ) ≡ 1 m o d    ( n ) a^{\phi(n)}\equiv 1\mod(n) aϕ(n)1mod(n)

证明

2.费马小定理
p p p 是质数,则对于任意的整数 a a a ,有 a p ≡ a m o d    ( p ) a^p\equiv a\mod(p) apamod(p)

证明:
可以发现,把式子变换为 a p − 1 ≡ 1 m o d    ( p ) a^{p-1}\equiv 1\mod(p) ap11mod(p) 后,就是欧拉定理中当 n n n 是质数的一种特殊情况。(非常妙)

3.欧拉定理的推论
若正整数 a , n a,n a,n 互质,则对于任意正整数 b b b ,有 a b ≡ a b m o d ϕ ( n ) m o d    ( n ) a^b\equiv a^{b mod \phi(n)}\mod(n) ababmodϕ(n)mod(n)

证明:
b = q ∗ ϕ ( n ) + r b=q*\phi(n)+r b=qϕ(n)+r ,其中 0 ≤ r < ϕ ( n ) 0\leq r<\phi(n) 0r<ϕ(n) ,即 r = b m o d    ϕ ( n ) r=b \mod\phi(n) r=bmodϕ(n)。 便有:
a b ≡ a q ∗ ϕ ( n ) + r a^b\equiv a^{q*\phi(n)+r} abaqϕ(n)+r
因为同余式右边 = ( a ϕ ( n ) ) q ∗ a r ≡ a r m o d    ( n ) =(a^{\phi(n)})^q*a^r \equiv a^r\mod(n) =(aϕ(n))qararmod(n)
所以可得 a b ≡ a b m o d ϕ ( n ) m o d    ( n ) a^b\equiv a^{b mod \phi(n)}\mod(n) ababmodϕ(n)mod(n)

特别地,当 a , n a,n a,n 不一定互质且 b > ϕ ( n ) b>\phi(n) b>ϕ(n) 时,有 a b ≡ a b m o d    ϕ ( n ) + ϕ ( n ) m o d    ( n ) a^b\equiv a^{b\mod\phi(n)+\phi(n)}\mod(n) ababmodϕ(n)+ϕ(n)mod(n)

4.裴蜀定理(Bézout)
对于任意整数 a , b a,b a,b,存在一对整数 x , y x,y x,y,满足 a x + b y = gcd ⁡ ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b)

对于 x = 1 , y = 0 x=1,y=0 x=1,y=0的情况时显然成立的,更一般的证明要用到欧几里得算法递归过程的数学归纳。
更一般情况的证明

相关知识和算法:

1.扩展欧几里得算法
扩展欧几里得算法的核心是裴蜀定理,利用欧几里得算法的递归流程进行 a x + b y = gcd ⁡ ( x , y ) ax+by=\gcd(x,y) ax+by=gcd(x,y) 方程的特解 x 0 , y 0 x_0,y_0 x0,y0 求解。
定义变量 x 0 , y 0 , d x_0,y_0,d x0,y0,d,分别表示特解和 gcd ⁡ ( a , b ) \gcd(a,b) gcd(a,b),通过如下程序求解。

int exgcd(int a,int b,int &x,int &y){   //int 类型版本
	if(b==0) {x=1,y=0;return a;}//	求gcd(a,b),得到第一步特解x=1,y=0; 
	int d=exgcd(b,a%b,x,y);
	int z=x;
	x=y;y=z-y*(a/b); 
	return d;
}

为薄绿献上心脏!

7. 名人名言

数论很简单,就是头冷。——数论之神yky
贪心做数论,暴力做组合。——chunzhen
暴力拿国二,贪心拿国一。——神lyc
*未经作者允许禁止转载

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值