判断一个数n
是否是质数,可以使用试除法,时间复杂度是 O ( n ) O(\sqrt n) O(n)的。
现在的问题是求1~n
中的所有质数,如果一个一个判断的话,时间复杂度是 O ( n × n ) O(n \times \sqrt n) O(n×n)的,不可取。
筛质数
所谓的筛质数是指:给定一个正整数n,输出1~n中的质数。
存在三种筛质数的方法:(1)朴素法筛质数;(2)埃拉托色尼筛选法;(3)线性选法。
这三种方法对数据的存储是相同的,如下:
const int N = 1000010;
int primes[N], cnt; // primes[0]~primes[cnt-1]存储的是0~n中所有的质数(从小到大)
bool st[N]; // st[i] == true说明i不是质数
(1)朴素法筛质数
基本思路:将2~n中所有数的倍数都删掉,剩余的数据就是质数。
从2一直遍历到n,如果当前st[i]==false
,说明i
是质数,因为它没被2~i-1
筛掉,说明2~i-1
都不能整除i
,根据定义i
是质数。
时间复杂度分析:我们要删除2的所有倍数,3的所有倍数,…,因此计算次数为 n 2 + n 3 . . . + n n = n × ( 1 2 + 1 3 . . . + 1 n ) \frac{n}{2} + \frac{n}{3} ... + \frac{n}{n} =n \times (\frac{1}{2} + \frac{1}{3} ... + \frac{1}{n}) 2n+3n...+nn=n×(21+31...+n1),其中 1 2 + 1 3 . . . + 1 n \frac{1}{2} + \frac{1}{3} ... + \frac{1}{n} 21+31...+n1是调和级数,该级数是发散的,但有: 1 2 + 1 3 . . . + 1 n = l n ( n ) \frac{1}{2} + \frac{1}{3} ... + \frac{1}{n}=ln(n) 21+31...+n1=ln(n),因此时间复杂度为 O ( n × l o g ( n ) ) O(n\times log(n)) O(n×log(n))。
// 朴素法筛质数,时间复杂度:O(n*log(n))
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}
(2)埃拉托色尼筛选法(埃式筛法)
该方法是对(1)的一个优化。
基本思路:将2~n中所有质数的倍数都删掉,剩余的数据就是质数。
从2一直遍历到n,如果当前st[i]==false
,说明i
是质数,因为它没被2~i-1
中的质数筛掉,说明2~i-1
中的质数都不能整除i
,则i
是质数。
时间复杂度分析:这里大致估计一下,首先由质数定理:1~n中质数的个数大约为 n l n ( n ) \frac{n}{ln(n)} ln(n)n个,本来要筛n个数,但是现在只需要筛大约为 n l n ( n ) \frac{n}{ln(n)} ln(n)n个数,因此时间复杂度为 n / l o g ( n ) × l o g ( n ) = n n / log(n) \times log(n)=n n/log(n)×log(n)=n,为 O ( n ) O(n) O(n)量级的。
实际上,埃式筛法准确的时间复杂度为 O ( n × l o g ( l o g ( n ) ) ) O(n \times log(log(n))) O(n×log(log(n))),这是因为我们的计算次数是 n 2 + n 3 + n 5 + . . . = n × ( 1 2 + 1 3 + 1 5 . . . ) = n × l o g ( l o g ( n ) ) \frac{n}{2} + \frac{n}{3} + \frac{n}{5} + ... =n \times (\frac{1}{2} + \frac{1}{3} + \frac{1}{5} ... ) = n \times log(log(n)) 2n+3n+5n+...=n×(21+31+51...)=n×log(log(n))。
// 埃拉托色尼筛选法,时间复杂度:O(n*log(log(n)))
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}
}
(3)线性选法
这和前两种方法没有什么关系,其是另一种思路。
基本思路:每个数只用它最小的质因子筛掉。
时间复杂度分析:因为每个数只会被它最小的质因子筛掉,因此每个数只会被遍历一次,时间复杂度是 O ( n ) O(n) O(n)的,当 n = 1 0 6 n=10^6 n=106时,线性筛法和埃式筛法效率差不多,当 n = 1 0 7 n=10^7 n=107时,线性筛法速度是埃式筛法的两倍。
// 线性选法,时间复杂度:O(n)
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
// 线性选法,时间复杂度:O(n)
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) prime[cnt++] = i;
// 这里没必要写 j < cnt, 因为i如果为合数的话,枚举到i的最小质因子后一定会停下来
// i如果为质数的话,枚举到primes[cnt - 1]=i时一定会停下来
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
/**
* primes[j]记为pj,因为从小到大枚举质因子,所以:
* (1) i % pj == 0 说明:pj是i的最小质因子,pj也是 pj*i的最小质因子
* (2) i % pj != 0 说明:pj一定小于i的所有质因子,pj也是 pj*i的最小质因子
* 另外:对于任意一个合数x,都会被筛掉:假设pj是x的最小质因子,当i枚举到
* x/pj 的时候,pj已经被枚举过(pj <= x/pj),x会pj被筛掉
* --> 因为每个合数都会被筛掉,而且每个数只会被它的最小质因子筛掉,因此每个数
* 只会被筛一次,时间复杂度是O(n)的
*/
问题描述
分析
代码
#include
const int N = 1000010;
int n;
int primes[N], cnt;
bool st[N]; // st[x]存储x是否被筛掉,为true代表是合数
/*********************************/
// // 朴素法筛质数: O(n * log(n))
// void get_primes() {
// for (int i = 2; i <= n; i++) {
// if (!st[i]) primes[cnt++] = i;
// for (int j = i + i; j <= n; j += i) st[j] = true;
// }
// }
/*********************************/
// // 埃拉托色尼筛法: O(n * log(log(n))
// void get_primes() {
// for (int i = 2; i <= n; i++) {
// if (!st[i]) {
// primes[cnt++] = i;
// for (int j = i + i; j <= n; j += i) st[j] = true;
// }
// }
// }
/*********************************/
// 线性筛法: O(n)
void get_primes() {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] * i <= n; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
scanf("%d", &n);
get_primes();
printf("%d\n", cnt);
return 0;
}
问题描述
分析
本题中要求求解在区间[L, R]
之间相邻质数距离的最小值和最大值,我们筛出[L, R]
之间的质数,然后遍历一遍即可得到答案。
因为线性筛法只能从1开始筛,因此一种基本做法是将1~R
中的所有质数筛出来,然后在区间[L, R]
中扫描一遍即可。但是这种做法对于本题不可行,因为L、R
最大为 2 31 − 1 = 2147483647 2^{31} - 1 = 2147483647 231−1=2147483647,二十多亿的计算量,空间也会超,不可行。
仔细读题可以发现 R − L ≤ 1 0 6 R - L \le 10 ^ 6 R−L≤106,我们可以考虑从这一点出发,考入如何只筛区间[L, R]
,对于一个合数x
,则其一定存在一个质因子p
,且 p ≤ n p \le \sqrt n p≤n,因此我们只需要将 1 1 1~ 2 31 − 1 \sqrt {2 ^ {31} - 1} 231−1之间的质数筛出来,大于需要筛除1~50000
之间的质数即可。
则对于[L, R]
之间的任意一个合数x
,则其必定存在一个质因子p
,其值在1~50000
之间且 p < x p < x p<x;反之也成立。即x
是合数 ⟺ \iff ⟺x
存在一个小于自己的质因子。
因此,本题的步骤是:
(1)找出1~50000
中所有的质因子;
(2)对于1~50000
中的每个质数p
,将[L, R]
中的所有p
的倍数筛掉(至少是两倍);
(3)遍历[L, R]
之间的质数,找出距离最小值和最大值。
现在考虑第(2)步如何处理:如何枚举[L, R]
中所有p
的倍数呢?我们只需要找到大于等于L
的最小的p
的倍数即可,该数值为 ⌈ L p ⌉ × p = ⌊ L + p − 1 p ⌋ × p \lceil \frac{L}{p} \rceil \times p = \lfloor \frac{L + p - 1}{p} \rfloor \times p ⌈pL⌉×p=⌊pL+p−1⌋×p。另外至少要是两倍,因此需要从 m a x ( p × 2 , ⌊ L + p − 1 p ⌋ × p ) max(p \times 2, \lfloor \frac{L + p - 1}{p} \rfloor \times p) max(p×2,⌊pL+p−1⌋×p)开始枚举。
时间复杂度:假设区间长度为 1 0 6 10^6 106,对于每个质数p
,最多有 1 0 6 p \frac{10^6}{p} p106个p
的倍数,因此
1 0 6 2 + 1 0 6 3 + 1 0 6 5 + . . . = 1 0 6 × ( 1 2 + 1 3 + 1 5 + . . . ) = 1 0 6 × l o g ( l o g ( 1 0 6 ) ) \frac{10^6}{2} + \frac{10^6}{3} + \frac{10^6}{5} + ... = 10 ^ 6 \times (\frac{1}{2} + \frac{1}{3} + \frac{1}{5} + ...) = 10^6 \times log(log(\sqrt{10^6})) 2106+3106+5106+...=106×(21+31+51+...)=106×log(log(106))
即时间复杂度为 O ( n × l o g ( l o g ( n ) ) ) O(n \times log(log(\sqrt{n}))) O(n×log(log(n)))的。
代码
#include
#include
using namespace std;
typedef long long LL;
const int N = 1000010;
int primes[N], cnt; // 刚开始存储[1~50000]之间的质数, 然后被复用, 存储[L, R]之间的质数
int st[N]; // 也会被复用, 表示是否被筛掉
void init(int n) {
memset(st, 0, sizeof st);
cnt = 0;
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] * i <= n; j++) {
st[i * primes[j]] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
int l, r;
while (cin >> l >> r) {
// (1) 找出1~50000中所有的质因子
init(50000);
// (2) 对于1~50000中的每个质数p,将[L, R]中的所有p的倍数筛掉(至少是两倍)
memset(st, 0, sizeof st);
for (int i = 0; i < cnt; i++) {
LL p = primes[i];
for (LL j = max(p * 2, (l + p - 1) / p * p); j <= r; j += p)
st[j - l] = true; // 代表区间[L, R]中的数据j是合数
}
cnt = 0;
for (int i = 0; i <= r - l; i++)
if (!st[i] && i + l >= 2) // i+l = 1时,1不是质数
primes[cnt++] = i + l;
if (cnt < 2) puts("There are no adjacent primes.");
else {
int minp = 0, maxp = 0;
for (int i = 0 ; i + 1 < cnt; i++) {
int d = primes[i + 1] - primes[i];
if (d < primes[minp + 1] - primes[minp]) minp = i;
if (d > primes[maxp + 1] - primes[maxp]) maxp = i;
}
printf("%d,%d are closest, %d,%d are most distant.\n",
primes[minp], primes[minp + 1],
primes[maxp], primes[maxp + 1]);
}
}
return 0;
}
问题描述
分析
阶乘的质因数分解: n ! n! n!所有的质因子一定是小于等于n的,否则如果存在大于n的质因子,说明该质因子一定是某几个数相乘得到的,违反质数的定义。
(1)求出1~n中所有的质数,可以使用线性法筛质数;
(2)枚举某个质数p,则其在n!的质因数分解中出现的次数为:
⌊ n p ⌋ + ⌊ n p 2 ⌋ + . . . . . . \lfloor \frac{n}{p} \rfloor + \lfloor \frac{n}{p^2} \rfloor + ...... ⌊pn⌋+⌊p2n⌋+......
该方法求阶乘 n ! n! n!的质因数分解的时间复杂度大约为 O ( n ) O(n) O(n),因为1~n中大约有 n l o g ( n ) \frac{n}{log(n)} log(n)n个质数,求每个质数出现次数计算大约为 l o g ( n ) log(n) log(n)的,相乘得到阶乘质因数分解时间复杂度大约为 O ( n ) O(n) O(n)的。
另外指的一提的是:AcWing 888. 求组合数 IV这一题中就用到了阶乘的质因数分解。
代码
#include
using namespace std;
const int N = 1000010;
int primes[N], cnt;
bool st[N];
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
int n;
scanf("%d", &n);
get_primes(n);
for (int i = 0; i < cnt; i++) {
int p = primes[i];
int s = 0, t = n;
while (t) s += t / p, t /= p;
printf("%d %d\n", p, s);
}
return 0;
}