首页 > 其他分享 >质数筛法

质数筛法

时间:2023-01-08 01:11:41浏览次数:56  
标签:Scanner 筛法 int 合数 public boolean new 质数

质数筛法

引入

原题链接:P3912 素数个数 - 洛谷

求 \(1\sim n\) 有多少个质数

朴素求法,时间复杂度 \(O(n\sqrt{n})\)

import java.util.Scanner;

public class Main {
    static boolean isPrime(int x) {
        if (x < 2) return false;
        for (int i = 2, sqrt = (int) Math.sqrt(x); i <= sqrt; ++i)
            if (x % i == 0) return false;
        return true;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final int n = sc.nextInt();
        int cnt = 0;
        for (int i = 1; i <= n; ++i) {
            if (isPrime(i)) ++cnt;
        }
        System.out.println(cnt);
    }
}

时间复杂度太高,考虑其他解法

埃氏筛

对于任意一个大于 \(1\) 的正整数 \(x\),那么他的 \(k\) 倍一定是合数(\(k>1\)),因为该数可以表示成 \(k\times x\)。

利用这个结论,如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。

时间复杂度 \(O(n\log\log n)\)

实现

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final int n = sc.nextInt();
        boolean[] isPrime = new boolean[n + 1];
        // 初始化2~n均为素数,逐个筛去
        for (int i = 2; i <= n; ++i) isPrime[i] = true;
        for (int i = 2; i <= n; ++i) {
            for (int j = 2 * i; j <= n; j += i) {
                isPrime[j] = false;
            }
        }
        int cnt = 0;
        for (boolean is : isPrime)
            if (is) ++cnt;
        System.out.println(cnt);
    }
}

优化

一、只筛质数的倍数

对于一个合数 \(a\times b\)

如果 \(a\) 是合数,且 \(a\) 是被 \(x\) 筛去的

那么合数 \(a\times b\) 会在筛 \(x\) 的倍数时被筛去

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final int n = sc.nextInt();
        boolean[] isPrime = new boolean[n + 1];
        for (int i = 2; i <= n; ++i) isPrime[i] = true;
        for (int i = 2; i <= n; ++i) {
            if (isPrime[i]) {
                for (int j = 2 * i; j <= n; j += i) {
                    isPrime[j] = false;
                }
            }
        }
        int cnt = 0;
        for (boolean is : isPrime)
            if (is) ++cnt;
        System.out.println(cnt);
    }
}

二、内循环从 \(i^2\) 筛其倍数

对于一个合数 \(a\times b\),其中 \(a>b>1\)

在用 \(b\) 筛掉这个合数之前,一定会被 \(a\) 先筛去,即一个合数会先被其较小的因子筛去

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final int n = sc.nextInt();
        boolean[] isPrime = new boolean[n + 1];
        for (int i = 2; i <= n; ++i) isPrime[i] = true;
        for (int i = 2; i <= n; ++i) {
            // 要注意乘法是否会超出int范围
            if (isPrime[i] && (long) i * i <= n) {
                for (int j = i * i; j <= n; j += i) {
                    isPrime[j] = false;
                }
            }
        }
        int cnt = 0;
        for (boolean is : isPrime)
            if (is) ++cnt;
        System.out.println(cnt);
    }
}

三、外循环只筛至 \(\sqrt n\)

上一个优化中提到,一个合数会先被其较小的因子筛去。

而对于 \([\sqrt n,n]\) 内的数均会被小于 \(\sqrt n\) 的因子筛去

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final int n = sc.nextInt();
        boolean[] isPrime = new boolean[n + 1];
        for (int i = 2; i <= n; ++i) isPrime[i] = true;
        for (int i = 2, sqrt = (int) Math.sqrt(n); i <= sqrt; ++i) {
            if (isPrime[i] && (long) i * i <= n) {
                for (int j = i * i; j <= n; j += i) {
                    isPrime[j] = false;
                }
            }
        }
        int cnt = 0;
        for (boolean is : isPrime)
            if (is) ++cnt;
        System.out.println(cnt);
    }
}

四、只筛奇数

我们知道除 \(2\) 以外的偶数均不是合数,因为所有偶数都可以写成 \(2\times x\) 的形式

对此,可以选择只筛奇数

奇数可以表示为 \(2\times x+1\),其中 \(x\in N\) ,\(x\) 对应质数表的下标

因此,一个数 \(n\) ,判断其是否为质数时,寻找下标为 \(\lfloor\dfrac{n}{2}\rfloor\) 的质数表所对应的值即可。

对于数组长度:

  1. 当 \(n\) 为偶数时,\(1\sim n\) 中最大的奇数为 \(n-1\)
    要用 \(2\times idx+1\) 表示 \(n-1\),即 \(idx_{max}=\dfrac{n-2}{2}\)
    因此需要空间个数为 \(\dfrac{n-2}{2}+1=\dfrac{n}{2}\)

  2. 当 \(n\) 为奇数时,\(1\sim n\) 中最大的奇数为 \(n\)
    要用 \(2\times idx+1\) 表示 \(n\),即 \(idx_{max}=\dfrac{n-1}{2}\)
    因此需要空间个数为 \(\dfrac{n-1}{2}+1=\dfrac{n+1}{2}\)

综上,需要数组空间为 \(\lceil\dfrac{n}{2}\rceil=\lfloor\dfrac{n+1}{2}\rfloor\)

注意此时每次增量应为原来的两倍

import java.util.Scanner;

public class Main {
    static boolean[] primeTable;

    static void initialize(final int n) {
        primeTable = new boolean[(n + 1) / 2];
        for (int i = 1; i < primeTable.length; ++i) primeTable[i] = true;
        for (int i = 3, sqrt = (int) Math.sqrt(n); i <= sqrt; i += 2) {
            if (primeTable[i / 2] && (long) i * i <= n) {
                for (int j = i * i; j <= n; j += 2 * i) {
                    primeTable[j / 2] = false;
                }
            }
        }
    }

    static boolean isPrime(int x) {
        if (x % 2 == 0) return x == 2;
        return primeTable[x / 2];
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final int n = sc.nextInt();
        initialize(n);
        int cnt = n < 2 ? 0 : 1;
        for (boolean is : primeTable)
            if (is) ++cnt;
        System.out.println(cnt);
    }
}

五、将数组意义改为不是素数

数组定义为如果为 \(true\) ,则它不是素数,这样可以不用自己初始化,编译器默认初始化为 \(false\)。

import java.util.Scanner;

public class Main {
    static boolean[] notPrime;

    static void initialize(final int n) {
        notPrime = new boolean[(n + 1) / 2];
        if (n > 0) notPrime[0] = true;
        for (int i = 3, sqrt = (int) Math.sqrt(n); i <= sqrt; i += 2) {
            if (!notPrime[i / 2] && (long) i * i <= n) {
                for (int j = i * i; j <= n; j += 2 * i) {
                    notPrime[j / 2] = true;
                }
            }
        }
    }

    static boolean isPrime(int x) {
        if (x % 2 == 0) return x == 2;
        return !notPrime[x / 2];
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final int n = sc.nextInt();
        initialize(n);
        int cnt = n < 2 ? 0 : 1;
        for (boolean is : notPrime)
            if (!is) ++cnt;
        System.out.println(cnt);
    }
}

线性筛

埃氏筛仍有优化思路,因为一些合数可能会被多个因子筛去

算术基本定理

任何一个大于 \(1\) 的自然数 \(N\),如果 \(N\) 不为质数,那么 \(N\) 可以唯一分解成有限个质数的乘积 \(N=P_1^{a_1}P_2^{a_2}P_3^{a_3}\cdots P_n^{an}\),这里 \(P_1<P_2<P_3\cdots<P_n\) 均为质数,其中指数 \(a_i\) 是正整数。这样的分解称为 \(N\) 的标准分解式。

我们选择用每个合数的最小质因子(或者说用这个合数的最大的因子)来筛去这个合数

这样就保证了每个合数只被筛去一次,那么时间复杂度就降低至 \(O(n)\) 了

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        int n = new Scanner(System.in).nextInt();
        int[] primes = new int[(n + 1) / 2];
        boolean[] isPrime = new boolean[n + 1];
        int tot = 0;
        for (int i = 2; i <= n; ++i) isPrime[i] = true;
        for (int i = 2; i <= n; ++i) {
            if (isPrime[i]) primes[tot++] = i;
            for (int prime : primes) {
                if (prime * i > n) break;
                isPrime[prime * i] = false;
                if (i % prime == 0) break;
            }
        }
        System.out.println(tot);
    }
}

PS:注意到筛法求素数的同时也得到了每个数的最小质因子,这是筛法求积性函数的铺垫

参考资料

筛法 - OI Wiki

标签:Scanner,筛法,int,合数,public,boolean,new,质数
From: https://www.cnblogs.com/Cattle-Horse/p/17033972.html

相关文章

  • 筛法
    筛法是重要的数论基础,介绍一下\(Eratosthenes\)和\(Euler\)筛法,还有线性筛求\(\varphi\)函数和\(\mu\)函数。\(Eratosthenes\)筛法也就是我们常说的埃氏筛法。时间复杂......
  • P1217 [USACO1.5]回文质数 Prime Palindromes
    题目题目描述因为151既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以151是回文质数。写一个程序来找出范围[a,b](5<=a<b<=100,000,000)(一亿)间的......
  • Prime number 质数相关
    什么是质数?在一个大于1的自然数中,除了1和此整数自身外,没法被其他自然数整除的数,比如:2,3,5,7,11...什么是合数?比1大但不是素数的数称为合数。1和0既非素数也非合数。合数是......
  • 质数判断——暴力方法、埃氏筛与线性筛
    质数判断  问题背景为:我们希望判断前n个数是否为质数,即得到isPrime[n+1](此处沿用java语言定义)数组。暴力方法  传统意义上的对质数的判断方法,是依据质数的定义—......
  • 用筛法求之N内的素数。(Java)
    解题思路:申请一个数组,从1-N初始化从第二个数开始,(2是素数),并且用循环把该数的倍数的数置为0然后访问下一个不是1的数(一定为素数),重复上面一个步骤在循环中把不是0......
  • 质数与约数
    质数与约数质数质数和合数的概念只针对于大于1的整数成立。质数:在大于1的整数中,如果只包含1和本身这两个约数,就被称为质数,或者素数。质数的判定试除法boolis_prim......
  • 08 回文质数 -Linux环境下的编译执行
    打开终端(terminal)输入cdm//打开m文件输入touchmain.cpp//新建main.cpp文件输入vimmain.cpp//使用vim来编写代码编写完毕后输入:wq//保存并退出输入g++main......
  • 判别质数
    某一天新发现的判断一个正整数是否是质数的方法,思路大概如下所示: #include<stdio.h> #include<stdlib.h> intmain(){  intn;  intflag=1;  pri......
  • 算法学习笔记(33)——质数
    质数质数一、试除法判定质数二、分解质因数三、筛质数3.1朴素筛法3.2埃氏筛法(Eratosthenes筛法)3.3欧拉筛法(线性筛法)一、试除法判定质数质数的定义:若......
  • 《尧驰质数判定公式》 回复
    《尧驰质数判定公式》     https://tieba.baidu.com/p/8168528921      22楼质数判定公式?这是重大成果吧?  多项式之父:这个公式只是预选公......