线性筛法(大概可能通俗易懂)

阅读之前

上下文结合观看更有助于理解,建议理解了简单一些的埃式筛后再进行阅读,本文主要讲解欧拉筛

本文直接或间接涉及到的一些数学概念:

​ 自然数:非负整数的集合。即一个数大于等于   0   ~0~  0 ,且为整数

 ​质数:自然数中除了   0 , 1   ~0,1~  01 以外只能被   1   ~1~  1 和自身整除的数

 ​合数:自然数中除了   0 , 1   ~0,1~  01 和质数以外的数

​ 因子:一个整数   n   ~n~  n 能够正好整除一个整数   m   ~m~  m ,则   m   ~m~  m    n   ~n~  n 的因子,但是自身不为自身的因子

​ 唯一分解定理:任何一个大于   1   ~1~  1 的整数   n   ~n~  n 都可以分解成若干个质因子的连乘积,如果不计各个质因子的顺序----比如   6 = 2 ∗ 3 = 3 ∗ 2   ~6=2*3=3*2~  6=23=32 ,如果计因子排列顺序那么有两种分解顺序,不计因子排列顺序的话只有一种----那么这种分解是唯一的。由德国数学家高斯于1801年证明

废话

一般筛法 / 埃式筛:先定义一个大小为   m a x n   ~maxn~  maxn 的数组代表将要进行筛选的   n   ~n~  n 个数,然后基于每个质数的两倍及更高倍数为非质数的思想对数组进行筛选,最后未筛选的数即为质数

线性筛法 / 欧拉筛:在一般筛法的基础上优化所得,   O ( n )   ~O(n)~  O(n) 的复杂度内求从   1   ~1~  1    n   ~n~  n 的范围内的质数。 避免了埃式筛对某些数字进行重复筛选的缺点,因此从   O ( n ∗ l o g l o g n )   ~O(n * log log n)~  O(nloglogn) 优化到   O ( n )   ~O(n)~  O(n) 

结论:将每一轮进行筛选的数   x   ~x~  x 表示为   i ∗ p r i m e [ j ]   ~i * prime[ j ]~  iprime[j]  (即   x = i ∗ p r i m e [ j ]   ~x = i * prime[ j ]~  x=iprime[j] )。保证   i   ~i~  i    x   ~x~  x 的最大因子,   p r i m e [ j ]   ~prime[ j ]~  prime[j]    x   ~x~  x 的最小因子(最小因子必为质数,如果不为质数,则最小因子可以分解为两个数的乘积, 与最小因子的定义矛盾)

  x   ~x~  x 能被分解成有限个质数相乘,(在这有限个质数当中可能包含了相同的质数,比如   300   ~300~  300 可以分解为   2 ∗ 2 ∗ 3 ∗ 5 ∗ 5   ~2 * 2 * 3 * 5 * 5~  22355 ,将这有限个质数从小到大排列,取出其中最小的质数即为最小因子,再将剩下的数乘起来即为最大因子(比如   300   ~300~  300 的最小因子为   2   ~2~  2 ,最大因子为   150   ~150~  150 

在筛选过程中将用到:

一个数表   n u m [ m a x n ]   ~num[maxn]~  num[maxn]  用于记录当前下标所代表的数是否为质数;

一个质数表   p r i m e [ m a x n ]   ~prime[maxn]~  prime[maxn]  用于从小到大依次储存质数,一个   c n t   ~cnt~  cnt 变量用于记录质数数量;

//质数表实际使用大小是不到   m a x n   ~maxn~  maxn 的,因为一定范围内的质数数量一定是少于这个范围的大小的。一个范围   n   ~n~  n 内大概有   n / l n ( n )   ~n/ln(n)~  n/ln(n) 个质数

两个变量   i   ~i~  i    j   ~j~  j 用于建立双层循环,外层循环的   i   ~i~  i 用于遍历   n u m   ~num~  num 数组作为最大因子;内层循环的   j   ~j~  j 用于遍历   p r i m e   ~prime~  prime 数组作为最小因子,同时筛除合数

主要问题在于如何筛选不遗漏、不重复,以及保证枚举最小因子

不遗漏和不重复的证明

除了   0 , 1   ~0,1~  0,1 外,我们在用双层循环从小到大枚举最大因子   i   ~i~  i 和最小因子   p r i m e [ j ]   ~prime[j]~  prime[j] 的乘积时,内层循环每次   i   ~i~  i    p r i m e [ j ]   ~prime[j]~  prime[j] 的组合都是不同的,可以视为我们在从小到大枚举每一个唯一分解式,自然是不遗漏和不重复的。
枚举的起点是   2   ~2~  2    2   ~2~  2 同时也是最小的唯一分解式(除   0 , 1   ~0,1~  0,1 外),   2   ~2~  2 之后是   3 , 2 ∗ 2 , 5 , 2 ∗ 3 , ⋅ ⋅ ⋅ ~3,2*2,5,2*3,···  322523⋅⋅⋅
因为每个数的质因数分解式都是唯一的(如果分解式不唯一,那么这个唯一的数就不唯一了),所以我们从小到大枚举唯一分解式一定能保证不遗漏和不重复

如何保证枚举最小因子

这样又带来了新的问题: 如何保证   p r i m e [ j ]   ~prime[j]~  prime[j] 就是最小质因子呢? 因为我们必须保证   p r i m e [ j ]   ~prime[j]~  prime[j]    x = i ∗ p r i m e [ j ]   ~x=i*prime[j]~  x=iprime[j] 的最小因子, 才能满足不重复和不遗漏的要求

  p r i m e   ~prime~  prime 数组中所有小于等于   i   ~i~  i 的最小质因子的质数   p r i m e [ j ]   ( j = 0 , 1 , 2 , 3 , ∼ ) ~prime[j]~(j=0,1,2,3,\sim)  prime[j] (j=0,1,2,3,)   i   ~i~  i 相乘后,都满足   p r i m e [ j ]   ~prime[j]~  prime[j]    x = i ∗ p r i m e [ j ]   ~x=i*prime[j]~  x=iprime[j] 最小质因子。在   p r i m e [ j ]   ~prime[j]~  prime[j] 等于   i   ~i~  i 的最小因子时结束对   i   ~i~  i 的筛选即可

证明(反证法):

假设枚举的最大因子   i = p r i m e [ 3 ] ∗ p r i m e [ 4 ]   ~i=prime[3]*prime[4]~  i=prime[3]prime[4] ,如果内层循环在   j = 3   ~j=3~  j=3 时仍然没结束筛选,接下来在   j = 4   ~j=4~  j=4 时,有   x = p r i m e [ 3 ] ∗ p r i m e [ 4 ] ∗ p r i m e [ 4 ]   ~x=prime[3]*prime[4]*prime[4]~  x=prime[3]prime[4]prime[4] ,而   p r i m e [ 4 ]   ~prime[4]~  prime[4] 非最小因子,这种情况下会与内层循环在   j = 3   ~j=3~  j=3 时,   i = p r i m e [ 4 ] ∗ p r i m e [ 4 ]   ~i=prime[4]*prime[4]~  i=prime[4]prime[4] 时重复。

所以,   p r i m e [ j ]   ~prime[j]~  prime[j] 等于   i   ~i~  i 的最小因子时结束对   i   ~i~  i 的筛选,可以保证枚举的   p r i m e [ j ]   ~prime[j]~  prime[j]    x   ~x~  x 的最小因子,否则   i   ~i~  i 的最小因子会小于   p r i m e [ j ]   ~prime[j]~  prime[j] ,与后面的筛选情况重复

比如:不能出现   i = 4 , p r i m e [ j ] = 3   ~ i = 4, prime [ j ] = 3~  i=4,prime[j]=3 乘积为   12   ~12~  12 的组合, 因为   3   ~3~  3 不为   12   ~12~  12 的最小因子。如果不避免这种情况就会和   i = 6 , p r i m e [ j ] = 2   ~i=6,prime[j]=2~  i=6,prime[j]=2 乘积也为   12   ~12~  12 的组合重复,而后者为正确的筛除方式

代码实现

const int maxn=1e5+1;

//全局变量自动初始化为0
bool num[maxn];

//1e5个数里质数数量肯定比1e5少的多, 这里随便除了个2优化一下空间, 也可以自己算一下n/ln(n)是多少
int prime[maxn / 2];
int cnt;

void Euler_Seive() {

    //初始用1标记2及以上的数全为质数. 也可以把0作为质数标记, 然后修改num0和num1为1
    for(int i = 2; i < maxn; i++)
        num[i] = 1;

    //i为最大因子
    for(int i = 2; i < maxn; i++) {

        //如果当前数未被筛除, 则加入质数表
        if (num[i])
            prime[cnt++] = i;

        //j遍历prime数组, prime[j]作为最小因子
        for (int j = 0; i * prime[j] < maxn; j++){
            //筛除该数
            num[i * prime[j]] = 0;

            //当prime[j]为i的最小因子时退出for循环,
            //因为当prime[j]大于i的最小因子时,  
            //n的最小因子存在于i的唯一分解式中
            if (!(i % prime[j]))
                break;
        }
    }
}


封面出处:AcWing 1356. 回文质数

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 线性筛是一种常用的求素数的方,它的思想简单且高效。下面是使用Java语言实现线性筛求素数的代码: ```java import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class LinearSieve { public static List<Integer> getPrimes(int n) { boolean[] isComposite = new boolean[n + 1]; List<Integer> primes = new ArrayList<>(); for (int i = 2; i <= n; i++) { if (!isComposite[i]) { primes.add(i); } for (int j = 0; j < primes.size() && i * primes.get(j) <= n; j++) { isComposite[i * primes.get(j)] = true; if (i % primes.get(j) == 0) { break; } } } return primes; } public static void main(String[] args) { int n = 100; List<Integer> primes = getPrimes(n); System.out.println("从 2 到 " + n + " 的素数为:"); for (int prime : primes) { System.out.print(prime + " "); } } } ``` 以上代码中,我们使用了一个布尔数组`isComposite`来标记是否为合数。初始时,将所有数都标记为非合数。然后从2开始,遍历到n,如果某个数i是合数,则跳过;如果是素数,则将其加入到素数列表中,并标记它的倍数为合数。遍历结束后,我们得到了从2到n的所有素数。 在main函数中,我们设置n为100,调用`getPrimes`函数获取从2到100的素数,并打印出来。 运行结果为:从 2 到 100 的素数为:2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97。 ### 回答2: 线性筛是一种高效地筛选出一定范围内的素数的算法。下面是用Java实现线性筛求素数的代码: ```java import java.util.*; public class LinearSieve { public static List<Integer> sieve(int n) { boolean[] isPrime = new boolean[n + 1]; Arrays.fill(isPrime, true); // 将所有数初始化为素数 List<Integer> primes = new ArrayList<>(); for (int i = 2; i <= n; ++i) { if (isPrime[i]) { primes.add(i); // 将素数加入结果列表 } for (int j = 0; j < primes.size() && i * primes.get(j) <= n; ++j) { isPrime[i * primes.get(j)] = false; // 将当前素数倍数标记为非素数 if (i % primes.get(j) == 0) { break; // 若当前数为素数倍数,跳出内层循环 } } } return primes; } public static void main(String[] args) { int n = 100; // 范围上限 List<Integer> primes = sieve(n); System.out.println("范围[2, " + n + "]内的素数有:"); for (int prime : primes) { System.out.print(prime + " "); } } } ``` 通过线性筛,我们首先将所有数初始化为素数,然后从2开始,将每个素数的倍数标记为非素数,直到筛选结束。最后,将筛选出的素数存入结果列表中。在上述代码中,我们以100为例,调用`sieve`方求解范围内的素数,并输出结果。 当我们运行上述代码时,将会得到范围[2, 100]内的素数列表: ``` 范围[2, 100]内的素数有: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 ``` 以上就是使用Java实现线性筛求素数的代码及结果。 ### 回答3: 线性筛是一种用于求解素数的算法,可以高效地找出某一个范围内的所有素数。下面是使用Java语言实现线性筛求素数的代码: ```java import java.util.ArrayList; import java.util.List; public class PrimeNumbers { public static List<Integer> getPrimeNumbers(int n) { List<Integer> primeNumbers = new ArrayList<>(); boolean[] isComposite = new boolean[n + 1]; for (int i = 2; i <= n; i++) { if (!isComposite[i]) { primeNumbers.add(i); } for (int j = 0; j < primeNumbers.size() && i * primeNumbers.get(j) <= n; j++) { isComposite[i * primeNumbers.get(j)] = true; if (i % primeNumbers.get(j) == 0) { break; } } } return primeNumbers; } public static void main(String[] args) { int n = 100; List<Integer> primeNumbers = getPrimeNumbers(n); System.out.println("在[2, " + n + "]范围内的素数有:"); for (int number : primeNumbers) { System.out.println(number); } } } ``` 这段代码使用了一个布尔数组isComposite来记录某个数是否为合数(非素数),初始时假设所有数都是质数,然后从2开始遍历到n,如果某个数i没有被标记为合数,就将其添加到素数列表中,并将i与已有的质数依次相乘,将其标记为合数。 运行以上代码,可以求解出2到100之间的所有素数。输出结果如下: ``` 在[2, 100]范围内的素数有: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 ``` 通过线性筛,我们可以高效地找到某个范围内的素数,而不需要遍历所有的数进行判断。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值