算法数论篇(质数)

一、判断质数

判断质数的思想实际上非常朴素,我们知到,当一个数不能被除了1和它本身之外的数整除的时候,这个数就是质数,当然,质数是从2开始算的,<= 1的数没有质数的概念

算法思想:我们从2~ n - 1 开始枚举,只要能找到一个数可以整除n,那么n就不是质数

给定 nn 个正整数 aiai,判定每个数是否是质数。

输入格式

第一行包含整数 nn。

接下来 nn 行,每行包含一个正整数 aiai。

输出格式

共 nn 行,其中第 ii 行输出第 ii 个正整数 aiai 是否为质数,是则输出 Yes,否则输出 No

数据范围

1≤n≤1001≤n≤100,
1≤ai≤231−11≤ai≤231−1

输入样例:

2
2
6

输出样例:

Yes
No
import java.util.*;

public class Main{
   
   public static boolean is_prime(int x) {
       if (x < 2) return false;   // < 2 的数它一定不是质数
       for (int i = 2; i <= x; i ++) { 
           if (x % i == 0) return false;  //可以被 1 和 它本身的之外的数整除的数一定不是质数
       }
       return true;
   }
   
   public static void main(String[] args) {
       Scanner sc = new Scanner(System.in);
       int n = sc.nextInt();
       while (n -- > 0) {
           int x = sc.nextInt();
           if (is_prime(x)) System.out.println("Yes");
           else System.out.println("No");
       }
   }
    
}

 时间复杂度?

我们通过观察代码可以发现,我们循环的次数固定是 2 ~ n - 1 ,因此时间复杂度稳稳的 O(N)

能否优化?

我们通过数学分析,如果一个数n可以被x整除,那么它一定也可以被 n / x 整除,那么我们循环的时候i的取值只需要最大达到根号n(i*i <= n),我们只需要计算 2 ~ 根号n,但是每次循环的话计算根号n比较费时间,我们可以采用 i <= n / i 的写法(不要采用 i *i <= n ,因为存在int溢出的情况)

优化完之后时间复杂度来到根号n

import java.util.*;

public class Main{
   
   public static boolean is_prime(int x) {
       if (x < 2) return false;   // < 2 的数它一定不是质数
       for (int i = 2; i <= x / i; i ++) {  //只是换了循环退出条件,时间复杂度由 N 到 根号 N
           if (x % i == 0) return false;  //可以被 1 和 它本身的之外的数整除的数一定不是质数
       }
       return true;
   }
   
   public static void main(String[] args) {
       Scanner sc = new Scanner(System.in);
       int n = sc.nextInt();
       while (n -- > 0) {
           int x = sc.nextInt();
           if (is_prime(x)) System.out.println("Yes");
           else System.out.println("No");
       }
   }
    
}

 

二、分解质因数

 在我们小学的时候我们学过,任何一个合数都可以被拆分为质数相乘的形式

同样的,一个数n如果是由多个质因数相乘得到的,那么它的质因数中最多只有一个可以大于根号n,(再多一个乘积就大于n了),因此我们依旧采用试除法可以很轻松得到分解质因数的代码

给定 nn 个正整数 aiai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。

输入格式

第一行包含整数 nn。

接下来 nn 行,每行包含一个正整数 aiai。

输出格式

对于每个正整数 aiai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。

每个正整数的质因数全部输出完毕后,输出一个空行。

数据范围

1≤n≤1001≤n≤100,
2≤ai≤2×1092≤ai≤2×109

输入样例:

2
6
8

输出样例:

2 1
3 1

2 3
import java.util.*;

public class Main{
    
    public static void divide(int n) {
        for (int i = 2; i <= n / i; i ++) {
            if (n % i == 0) {
                int res = 0;   // 计算指数
                while (n % i == 0) {   //只要这个数还可以被这个质数除 那就狠狠地除!
                    n = n / i;         //如果x可以被除尽,那么n最后一定是1
                    res++;
                }
                System.out.println(i + " " + res);
            }
        }
        if (n > 1) System.out.println(n + " " + 1);
        System.out.println();
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        while (n -- > 0) {
            int x = sc.nextInt();
            divide(x);
        }
    }
}

 

三、筛质数

给定一个正整数 nn,请你求出 1∼n1∼n 中质数的个数。

输入格式

共一行,包含整数 nn。

输出格式

共一行,包含一个整数,表示 1∼n1∼n 中质数的个数。

数据范围

1≤n≤1061≤n≤106

输入样例:

8

输出样例:

4

1、朴素筛法

首先说一下朴素做法地思想吧:

我们用一个st数组来标记素数,如果当前我们遍历地点没有被标记为合数,那它肯定是质数(下边会解释),我们把它添加到质数数组中即可,其次不管这个数是不是质数,我们都用它去标记他到n之间倍数为合数,好了,为什么遍历到当前的数没被标记过一定是质数呢?我们知道任何一个合数是一定可以被分为几个质数相乘地形式的,按我们之前地倍数更新法,合数一定会被标记,质数之所以没有被标记是因为它地因数只有自己和1,没有因数通过倍数来更新它!

import java.util.*;

public class Main{
    static int cnt = 0;
    static int[] primes = new int[1000010];
    static boolean[] st = new boolean[1000010];
    
    public static void find(int n) {
        for (int i = 2; i <= n; i ++) {
            if (!st[i]) {  //i 是质数 
                primes[cnt++] = i;     //存储质数
            }
            for (int j = i + i; j <= n; j += i) {  //更新合数
                st[j] = true;
            }
        }
        System.out.println(cnt);
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        find(n);
    }
}

2、埃氏筛

埃氏筛其实是对朴素做法地一种改进,我们只需要把质数的倍数标记为合数(合数的倍数一定是被质数更新过了,因为合数一定可以分解为质数相乘),这样优化之后的时间复杂度是O(nloglogn)

import java.util.*;

public class Main{
    static int N = 1000010;
    static int cnt = 0;
    static int[] primes = new int[N];
    static boolean[] st = new boolean[N];
    
    public static void find(int n) {
        for (int i = 2; i <= n; i ++) {
            if (!st[i]) { // i 一定是质数
                primes[cnt++] = i;
                for (int j = i + i; j <= n; j += i) {           //只是把更新操作放在了质数的情况里面
                    st[j] = true;
                }
            }
        }
        System.out.print(cnt);
    }
    
    public static void main(String[] args) {
        Scanner sc = new  Scanner(System.in);
        int n = sc.nextInt();
        find(n);
    }
}

3、线性筛

对于埃氏筛法的进一步改进,我们控制合数只会被它的最小质因子标记,时间复杂度O(n)

import java.util.*;

public class Main{
    static int N = 1000010;
    static int cnt;
    static int[] primes = new int[N];
    static boolean[] st = new boolean[N];
    
    public static void find(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;
            }
        }
        System.out.print(cnt);
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        find(n);
    }
}

解释一下更新合数的代码:

(简称prime【j】为 pj

1、当我们 i % pj == 0 时,我们可以肯定pj * i 的最小质因子一定是 pj (因为我们是用最小的质因子从小到大更新的),我们之后break掉,可以肯定这个合数只被它的最小质因子更新

2、如果 i % pj != 0,由于我们pj从小到大,所以i的最小质因子一定是大于 pj的,我们用pj更新标记pj * i 理所应当

3、为什么合数一定会被标记?

本质上还是因为合数一定可以被拆分为质数相乘的形式,我们一直在使用i* pj的方式标记合数,没有跳跃,所以合数一定会被标记

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者: 裴定一 / 祝跃飞 出版社: 科学出版社 出版年: 2002年09月 页数: 233 页 定价: 19.00 装帧: 平装 ISBN: 9787030106834 内容简介 · · · · · · 本书论述了算法数论的基本内容,其中包括:连分数、代数数域、椭圆曲线、素性检验、大整数因子分解算法、椭圆曲线上的离散对数、超椭圆曲线。本书的特点是内容涉及面广,在有限的幅内,包含了必要的预备知识和数学证明,尽可能形成一个完整的体系。并且本书的部分内容曾多次在中国科学院研究生院信息安全国家重点实验室和广州大学作为硕士研究生教材使用。 本书可作为信息安全、数论等专业的研究生教材及相关专业的研究人员、高等学校的教师和高年级学生的参考。 序 前言 第一章 整数的因子分解 1.1 唯一分解定理 1.2 辗转相除法(欧氏除法) 1.3 Mersenne素数和Fermat素数 1.4 整系数多项式 1.5 环Z[i]和Z[ω] 习题一 第二章 同余式 2.1 孙子定理 2.2 剩余类环 2.3 Euler函数ρ(m) 2.4 同余方程 2.5 原根 2.6 缩系的构造 习题二 第三章 二次剩余 3.1 定义及Euler判别条件 3.2 Legendre符号 3.3 Jacobi符号 习题三 第四章 特征 4.1 剩余系的表示 4.2 特征 4.3 原特征 4.4 特征和 4.5 Gauss和 习题四 第五章 连分数 5.1 简单连分数 5.2 用连分数表实数 5.3 最佳渐近分数 5.4 Legendre判别条件 习题五 第六章 代数数域 6.1 代数整数 6.2 Dedekind整环 6.3 阶的一些性质 第七章 椭圆曲线 7.1 椭圆曲线的群结构 7.2 除子类群 7.3 同种映射 7.4 Tate模和Weil对 7.5 有限域上的椭圆曲线 习题七 第八章 在密码学中的一些应用 8.1 RSA公钥密码 8.2 Uiffie-Hellman体制 8.3 ElGamal算法 8.4 基于背包问题的公钥密码 8.5 秘密共享 第九章 素性检验 9.1 Fermat小定理及伪素数 9.2 强伪素数及Miller-Rabin检验 9.3 利用n-1的因子分解的素性检验 9.4 利用n+1的因子分解的素性检验 9.5 分圆环素性检验 9.6 基于椭圆曲线的素性检验 第十章 大整数因子分解算法 10.1 连分数因子分解算法 10.2 二次筛法 10.3 Pollard的P-1因子分解算法 10.4 椭圆曲线因子分解算法 10.5 数域筛法 习题十 第十一章 椭圆曲线上的离散对数 11.1 椭圆曲线公钥密码 11.2 小步-大步法 11.3 家袋鼠和野袋鼠 11.4 MOV约化 11.5 FR约化 11.6 SSSA约化 11.7 有限域上离散对数的计算 第十二章 超椭圆曲线 12.1 超椭圆曲线的Jacobian 12.2 虚二次代数函数域 12.3 基于超椭圆曲线的公钥密码 附录 一些常用算法 A.1 不可约多项式的判别 A.2 有限域中平方根的求解 A.3 有限域上的分解 A.4 Hensel引理 A.5 格 A.6 Z[x]中多项式的分解 参考文献
内容简介: 本书论述了算法数论的基本内容,其中包括:连分数、代数数域、椭圆曲线、素性检验、大整数因子分解算法、椭圆曲线上的离散对数、超椭圆曲线。本书的特点是内容涉及面广,在有限的幅内,包含了必要的预备知识和数学证明,尽可能形成一个完整的体系。并且本书的部分内容曾多次在中国科学院研究生院信息安全国家重点实验室和广州大学作为硕士研究生教材使用。本书可作为信息安全、数论等专业的研究生教材及相关专业的研究人员、高等学校的教师和高年级学生的参考。 目录: 序 前言 第一章 整数的因子分解 1.1 唯一分解定理 1.2 辗转相除法(欧氏除法) 1.3 Mersenne素数和Fermat素数 1.4 整系数多项式 1.5 环Z和Z[ω] 习题一 第二章 同余式 2.1 孙子定理 2.2 剩余类环 2.3 Euler函数ρ(m) 2.4 同余方程 2.5 原根 2.6 缩系的构造 习题二 第三章 二次剩余 3.1 定义及Euler判别条件 3.2 Legendre符号 3.3 Jacobi符号 习题三 第四章 特征 4.1 剩余系的表示 4.2 特征 4.3 原特征 4.4 特征和 4.5 Gauss和 习题四 第五章 连分数 5.1 简单连分数 5.2 用连分数表实数 5.3 最佳渐近分数 5.4 Legendre判别条件 习题五 第六章 代数数域 6.1 代数整数 6.2 Dedekind整环 6.3 阶的一些性质 第七章 椭圆曲线 7.1 椭圆曲线的群结构 7.2 除子类群 7.3 同种映射 7.4 Tate模和Weil对 7.5 有限域上的椭圆曲线 习题七 第八章 在密码学中的一些应用 8.1 RSA公钥密码 8.2 Uiffie-Hellman体制 8.3 ElGamal算法 8.4 基于背包问题的公钥密码 8.5 秘密共享 第九章 素性检验 9.1 Fermat小定理及伪素数 9.2 强伪素数及Miller-Rabin检验 9.3 利用n-1的因子分解的素性检验 9.4 利用n+1的因子分解的素性检验 9.5 分圆环素性检验 9.6 基于椭圆曲线的素性检验 第十章 大整数因子分解算法 10.1 连分数因子分解算法 10.2 二次筛法 10.3 Pollard的P-1因子分解算法 10.4 椭圆曲线因子分解算法 10.5 数域筛法 习题十 第十一章 椭圆曲线上的离散对数 11.1 椭圆曲线公钥密码 11.2 小步-大步法 11.3 家袋鼠和野袋鼠 11.4 MOV约化 11.5 FR约化 11.6 SSSA约化 11.7 有限域上离散对数的计算 第十二章 超椭圆曲线 12.1 超椭圆曲线的Jacobian 12.2 虚二次代数函数域 12.3 基于超椭圆曲线的公钥密码 附录 一些常用算法 A.1 不可约多项式的判别 A.2 有限域中平方根的求解 A.3 有限域上的分解 A.4 Hensel引理 A.5 格 A.6 Z[x]中多项式的分解 参考文献 免责申明:此书是我在网络上获取的,希望对大家有用。资源版权归作者及其公司所有,如果你喜欢,请购买正版。~~~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值