【数论】—— 素数

素数

定义

因数只有 1 1 1 和这个数本身的数被称作素数。

注意: 1 1 1 既不是素数也不是合数, 2 2 2 是最小的素数。

两个关于素数的定理

唯一分解定理

对于任意大于 1 1 1 的整数 x x x,都可以分解成若干个素数的乘积:

x = p 1 a 1 × p 2 a 2 × p 3 a 3 × ⋯ × p n a n ( a i ∈ Z + ) x=p_1^{a_1}\times p_2^{a_2}\times p_3^{a_3}\times \cdots \times p_n^{a_n}(a_i \in \Z^+) x=p1a1×p2a2×p3a3××pnan(aiZ+)

比如 57 = 3 × 19 57=3 \times19 57=3×19 1704 = 2 3 × 3 × 71 ⋯ 1704=2^3 \times 3\times 71\cdots 1704=23×3×71

费马小定理

若存在正整数 a a a 与素数 p p p,且 gcd ⁡ ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1。则有 a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp)

证明就不给了,有兴趣可以上网找一下。费马小定理在后续会有很多用处。

素因数分解

知道了唯一分解定理,那怎样得到一个数的素因数分解呢?很简单,只需要由最小的素数开始枚举,遇到可以整除的便将这个素因数从原数中除掉,最后便能得到原数的素因数分解。

假设我们要分解: x x x,设 x = p 1 a 1 × p 2 a 2 × p 3 a 3 × ⋯ × p n a n ( a i ∈ Z + ) x=p_1^{a_1}\times p_2^{a_2}\times p_3^{a_3}\times \cdots \times p_n^{a_n}(a_i \in \Z^+) x=p1a1×p2a2×p3a3××pnan(aiZ+)

则我们第一次遇到可以整除的素因数便是 p 1 p_1 p1,于是我们便用 x x x 不断地去除以 p 1 p_1 p1,直到 x x x 的因数中不含 p 1 p_1 p1,也就是说除了 a 1 a_1 a1 次。然后以此类推即可。

代码

inline void prime_factorization(int x) {
	for(register int i = 2;i <= x;i ++)
		if(x % i == 0) {
			cout << i << "^";
			int cnt = 0;
			while(x % i == 0) {
				x /= i;
				cnt ++;
			}
			cout << cnt << " ";
		}
	cout << '\n';
	return;
}

素数的判别方法

试除法

试判断 x x x 是否为素数。

由素数的定义可知,素数的因数只有两个。所以我们可以直接暴力枚举除 1 1 1 以外小于 x x x 的数。

  • 如果有 1 < i < x 1<i<x 1<i<x i ∣ x i \mid x ix 就说明 x x x 有超过两个数的因数,所以 x x x 不是素数。
  • 如果枚举完了,但还是找不到整除 x x x 的数,就说明 x x x 是素数。

时间复杂度为 O ( n ) O(n) O(n),能不能优化呢?答案是可以的。

由因数的性质可知,若正整数 n n n 有一因数 i i i i ≤ n i\le\sqrt{n} in ,则 n n n 必有另外一因数 j j j j ≥ n j\ge\sqrt{n} jn 。在 i = n i=\sqrt{n} i=n 时,有 i = j i=j i=j

根据这一个性质,我们可以只枚举 2 2 2 x \sqrt{x} x 的数进行试除,时间复杂度优化到 O ( n ) O(\sqrt{n}) O(n )

代码
inline bool is_prime(int x) {
	for(register int i = 2;i <= sqrt(x);i ++)
		if(x % i == 0)
			return false;
	return true;
}

但当题目要求求出一串数中的素数时,试除法的时间复杂度就变成了 O ( n n ) O(n\sqrt{n}) O(nn ),这在 n n n 过大时是不能接受的。所以,我们便有了素数的筛法。

埃氏筛

素数的第一种筛法。那何为筛法呢?顾名思义,就是把不是素数的数筛掉,这样剩下的就只有素数了。那怎么筛呢?

由唯一分解定理可知,任意一个大于 1 1 1 的合数都可以表示成多个素数相乘。此时不难想到,对于每一个素数,我们可以把它的所有的倍数筛掉,这样就可以把题目给定范围内的合数都筛掉,只留下素数。记得要特判 1 1 1,因为它既不是素数也不是合数。这样我们便有了素数的第一种筛法。鉴于这个筛法是古希腊数学家埃拉托斯特尼发明的,所以我们便称这个筛法为埃氏筛

给大家模拟一下,加深理解:

此时,若我们要筛出 12 12 12 以内的素数。( f a l s e false false 表示是素数, t r u e true true 则反之)

  1. 首先,我们要特判 1 1 1
1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 11 12 12 12
f l a g flag flag t r u e \color{red}true true f a l s e false false f a l s e false false f a l s e false false f a l s e false false f a l s e false false f a l s e false false f a l s e false false f a l s e false false f a l s e false false f a l s e false false f a l s e false false
  1. 接着,我们从 2 2 2 开始一个一个的往后筛:
1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 11 12 12 12
f l a g flag flag t r u e true true f a l s e \color{orange}false false f a l s e false false t r u e \color{red}true true f a l s e false false t r u e \color{red}true true f a l s e false false t r u e \color{red}true true f a l s e false false t r u e \color{red}true true f a l s e false false t r u e \color{red}true true
1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 11 12 12 12
f l a g flag flag t r u e true true f a l s e false false f a l s e \color{orange}false false t r u e true true f a l s e false false t r u e \color{red}true true f a l s e false false t r u e true true t r u e \color{red}true true t r u e true true f a l s e false false t r u e \color{red}true true
  1. 此时,我们发现 4 4 4 已经被筛掉了,所以我们直接跳过它去筛 5 5 5
1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 11 12 12 12
f l a g flag flag t r u e true true f a l s e false false f a l s e false false t r u e true true f a l s e \color{orange}false false t r u e true true f a l s e false false t r u e true true t r u e true true t r u e \color{red}true true f a l s e false false t r u e true true
  1. 我们发现,在筛到 7 7 7 时,它的倍数已经超出了需要求的范围,所以我们便可以停止了。

重新回看表格,所有的 f a l s e false false 即为素数:

1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 11 12 12 12
f l a g flag flag t r u e true true f a l s e \color{red}false false f a l s e \color{red}false false t r u e true true f a l s e \color{red}false false t r u e true true f a l s e \color{red}false false t r u e true true t r u e true true t r u e true true f a l s e \color{red}false false t r u e true true
代码
int n;
bool flag[MAXN];

void prime(int n) {
	flag[1] = true;
	for(register int i = 2;i <= n;i ++)
		if(!flag[i])
			for(register int j = 2;j * i <= n;j ++)
				flag[j * i] = true;
	for(register int i = 1;i <= n;i ++)
		if(!flag[i])
			cout << i << " ";
	return;
}

埃氏筛的时间复杂度是: O ( n log ⁡ log ⁡ n ) O(n \log \log n) O(nloglogn)。由于证明过程过于繁琐,这里就不赘述了,有兴趣可以去搜一下。一般来说,知道时间复杂度就可以了。

线性筛

埃氏筛的时间复杂度已经很优了,但当题目的数据范围达到 1 0 9 10^9 109 时,埃氏筛便处理不了了。此时,我们便需要用到线性筛了。

顾名思义,线性筛的时间复杂度在埃氏筛上的基础上优化到了 O ( n ) O(n) O(n)。具体优化在哪里?

不难发现,许多正整数都有着超过 1 1 1 个的素因数。而在埃氏筛中,正整数会被其所有的素因数都筛一遍,这里浪费了很多时间。但其实,我们只需要用每个正整数最小的素因数去筛便可以了,线性筛就在这个点上优化了。

拿刚刚的例子讲解一下:

在小于 12 12 12 的数中有 6 = 2 × 3 , 10 = 2 × 5 , 12 = 2 2 × 3 6=2\times 3,10=2\times5,12=2^2\times3 6=2×3,10=2×5,12=22×3。这三个数都被筛了多次。

1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 11 12 12 12
f l a g flag flag t r u e true true f a l s e false false f a l s e false false t r u e true true f a l s e false false t r u e \color{red}true true f a l s e false false t r u e true true t r u e true true t r u e \color{red}true true f a l s e false false t r u e \color{red}true true

而在线性筛中, 6 6 6 10 10 10 12 12 12,都只会被 2 2 2 筛掉 1 1 1 次。

具体的实现方法便是只用小于等于自己最小的素因数的素数来筛掉合数。

算法流程:

  • 首先与埃氏筛一样,要特判 1 1 1 的情况。

  • 然后,把找到的素数都放进一个 prime 数组里面。假设我们现筛到了 i i i,则我们便会用 prime 数组中的素数与 i i i 相乘,把相乘所得到的结果都标记为合数。当 i i i 除以 prime 中的素数余数为 0 0 0 时停止(先筛完再停止)。这样便做到了用最小的素数筛掉合数。

为什么这是正确的呢?我们来证明一下:

设 prime 数组中的素数为 k 1 k_1 k1 k 2 k_2 k2 ⋯ \cdots k n k_n kn,且 k 1 ≤ k 2 ≤ ⋯ ≤ k n k_1\le k_2 \le \cdots \le k_n k1k2kn

  • i i i 为素数。

    k i ∤ i k_i\nmid i kii 时说明 k i k_i ki i i i 小,所以 k i k_i ki 是正整数 k i × i k_i \times i ki×i 最小的素因数。

    k i ∣ i k_i \mid i kii 时说明 k i = i k_i=i ki=i,此时, k i k_i ki i i i 同为 k i × i k_i \times i ki×i 的最小素因数。

  • i i i 不为素数,设其分解素因数形式为: p 1 a 1 × p 2 a 2 × ⋯ × p n a n p_1^{a_1}\times p_2^{a_2} \times \cdots \times p_n^{a_n} p1a1×p2a2××pnan

    则当 k i ∤ i k_i \nmid i kii 时,同样的, k i k_i ki 必然比 p 1 p_1 p1 要小,所以 k i k_i ki 肯定是正整数 k i × i k_i \times i ki×i 的最小素因数。

    k i ∣ i k_i \mid i kii 且是第一次整除时说明 k i = p 1 k_i=p_1 ki=p1,此时, k i k_i ki p 1 p_1 p1 同为 k i × i k_i \times i ki×i 的最小素因数。

代码
int prime[MAXN] , cnt;
bool is_prime[MAXN];

inline void Init(int x) {
	is_prime[1] = true;
	for(register int i = 2;i <= x;i ++) {
		if(!is_prime[i])
			prime[++ cnt] = i;
		for(register int j = 1;j <= cnt && i * j <= x;j ++) {
			is_prime[i * prime[j]] = true;
			if(i % prime[j])
				break;
		}
	}
	return;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值