给定一个正整数 n,请你求出 1∼n 中质数的个数。
输入格式
共一行,包含整数 n。
输出格式
共一行,包含一个整数,表示 1∼n 中质数的个数。
数据范围
1≤n≤106
输入样例:
8
输出样例:
4
朴素做法
for循环 2~n ,
判断当前数是不是合数,不是的话把他加入素数集合中
for循环将当前数的倍数都断定为合数
时间复杂度: O(nlogn)
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int res = getPrimers(n);System.out.println(res);}static int getPrimers(int n) {boolean[] st = new boolean[n + 1];//这个数组其实有没有都行,因为目的是求质数个数,不是每个质数//后面的线性筛法需要,所以保留一下int[] primers = new int[n];int idx = 0;for (int i = 2; i <= n; i++) {//判断当前数是否为素数if (!st[i]) primers[idx++] = i;//用当前数更新后面的数for (int j = i; j <= n; j += i) {st[j] = true;}}return idx;}}
埃氏筛法
相较于线性筛法,将内层for循环放入if内,意思是只用质因子更新后面的合数。
这也很好理解,因为一个合数必然能被分解为质因子的乘积。
时间复杂度: O(nloglogn)
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int res = getPrimers(n);System.out.println(res);}static int getPrimers(int n) {boolean[] st = new boolean[n + 1];int[] primers = new int[n];int idx = 0;for (int i = 2; i <= n; i++) {//判断当前数是否为素数if (!st[i]) {primers[idx++] = i;//用素数更新后面的数for (int j = i; j <= n; j += i) {st[j] = true;}}}return idx;}}
线性筛法
相较于埃氏筛法又有了改进。
对于埃氏筛法来说,有的合数会被筛多次,例如:求1-100中的质数,对于12来说,当i遍历到2时st[12]被置为true,当i遍历到3时,st[12]又被置为true。也就是说一个合数会被所有比它小的质因子都筛一遍。
线性筛法的原理就是每个合数都是被它的最小质因子筛掉的
时间复杂度O(n)
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int res = getPrimers(n);System.out.println(res);}static int getPrimers(int n) {boolean[] st = new boolean[n + 1];int[] primers = new int[n];int idx = 0;for (int i = 2; i <= n; i++) {//判断当前数是否为素数if (!st[i]) primers[idx++] = i;for (int j = 0; primers[j] <= n / i; j++) {//当i % primers[j] != 0时primers[j]与i互质,i*primers[j]的最小质因子就是primers[j]st[primers[j] * i] = true;//当i % primers[j] == 0 时,//i的最小质因子是primers[j],所以i*primers[j]的最小质因子就是primers[j]if (i % primers[j] == 0) break;}}return idx;}}
// 20220405 新版代码
几个问题
- 内层
if一定能使内层for循环结束吗?
答:一定能。因为primers数组存储了 2~i 之间的所有质因子
如果i是质数,一定能结束,因为primers数组当前的最后一个质数就是i, i % i == 0
如果i是合数,也一定能结束,因为i能被分解为质数的乘积且这些质数一定小于i,由于primers中已经存储了 2~i 的所有质数,当 primers[j] 为i的最小质因子时 i % primers[j] == 0
- 合数一定都会被筛掉吗?
答:一定会。因为合数能分解为质数的乘积,考虑这样一种分解,对于合数k,假定它的最小质因子为a,那么它的另一个质因子 b = k / a 。
外层循环i的范围是 2~n ,包含了所有b的范围,也就是说,只要我们能找到k的最小质因子a,就可以筛掉 k = a * b
如果当前i是质数,i一定会被存入 primers 数组的当前最后一个位置上。只要 primers[j] * i <= n,就能筛掉一个合数
如果当前i是合数,i的最小质因子一定在primers中,当 i % primer[j] == 0 时说明当前i的最小质因子就是primers[j],那么对于 i * primers[j] 来说,它的最小质因子还是primers[j] ,应该被筛掉。且此时应退出内层循环,不能再继续筛,因为此时 i * primers[j] 不是被primers[j]筛掉的,而是被i的最小质因子筛掉的
应用
:::tips 如果题目要求对多个数字进行算术基本定理分解,可以考虑结合线性筛进行快速计算 :::
AcWing 1295. X的因子链
import java.util.*;public class Main {static int N = (1 << 20) + 10;static int[] primers = new int[N], minp = new int[N];static boolean[] st = new boolean[N];static int cnt;public static void main(String[] args) {Scanner sc = new Scanner(System.in);get_primers(N - 1);while (sc.hasNext()) {int x = sc.nextInt();deal(x);}}static void deal(int x) {int[] c = new int[30];int tot = 0, idx = 0;while (x > 1) {int k = minp[x], cc = 0;while (x % k == 0) {cc++;tot++;x /= k;}c[idx++] = cc;}long res = 1;for (int i = 1; i <= tot; i++)res *= i;for (int i = 0; i < idx; i++) {for (int j = 1; j <= c[i]; j++)res /= j;}System.out.println(tot + " " + res);}static void get_primers(int n) {for (int i = 2; i <= n; i++) {if (!st[i]) {minp[i] = i;primers[cnt++] = i;}for (int j = 0; primers[j] * i <= n; j++) {st[primers[j] * i] = true;minp[primers[j] * i] = primers[j];if (i % primers[j] == 0) break;}}}}
AcWing 196. 质数距离
思路:
直接用线性筛会爆空间,爆时间
考虑到左右边界之间相差只有106,想要快速找出这一段区间内的质数,有一个Trick
如果一个数x是合数,一定存在一个小于等于sqrt(x)的质数是它的因子。
故可以考虑用线性筛法预处理50000以内的所有质数
对于一段给定区间,用50000以内的素数快速判断区间中的每个数是不是合数
总的时间复杂度包括预处理 + 求一段区间内的质数
前者为线性,后者可以类比埃氏筛法的时间复杂度,基本是线性
故总时间复杂度为线性
实现时需要考虑溢出问题!以及1不是素数!
