数学之数论

目录

高密度计算

概念

数论

模运算

快速幂

最大公约数 GCD

最小公倍数 LCM

扩展欧几里得算法与二元一次方程的整数解

同余与逆元

素数


高密度计算

概念

指参与运算的数大大超出了标准数据类型所能表示的范围的运算

c/c++中,最大数据类型只有64位,如果需要处理更大的数,只能用数组来模拟,把大数的每一位存储在数组中

java中,可以直接计算。有两个类:BigInteger 和 BigDecimal,分别表示大整数类和大浮点数类,存在于 java.math.* 包中(java虽然能处理大数,但对于规模过大的问题,java也不能做)

例如:hdu 1024题:输入整数 N(0<=N<=10000),输出 N!

java代码如下:

import java.math.BigInteger;
import java.util.Scanner;
public static void main(String args[]){
        Scanner sc = new Scanner(System.in);
        while(sc.hasNext()){
            int n = sc.nextInt();
            BigInteger ans = BigInteger.ONE;
            for(int i=1; i<=n; i++){
                ans = ans.multiply(BigInteger.valueOf(i));
            }
            System.out.println(ans);
        }
        sc.close();
    }

数论

研究整数性质的数学分支

模运算

定义模运算:a 除以 m 的余数,记为:a mod m = a % m

取模结果满足 0<= a mod m <=m-1

性质:加:(a+b) mod m = ((a mod m) + (b mod m)) mod m

           减:(a-b) mod m = ((a mod m) - (b mod m)) mod m

           乘:(a*b) mod m = ((a mod m) * (b mod m)) mod m

           除:(\frac{a}{b})\ mod\ m\ =((\frac{a}{b})\ mod\ m)((bk)\ mod\ m) = (\frac{a}{b}bk)\ mod\ m = (ak)\ mod\ m,其中 k 是 b 的逆元

快速幂

快速幂就是高效地算出 a^{n}:先算a^{2},然后继续计算平方(a^{2})^{2},一直算到 n 次幂。复杂度为 O(\log_{2}n)

代码如下:

int fastPow(int a, int n){
        if(n==1) return a;
        int temp = fastPow(a,n/2);    //分治
        if(n%2 == 1){
            return temp*temp*a;    //奇数个a
        }else                      //偶数个a
            return temp*temp;
    }

还有一种方法:用位运算处理快速幂,复杂度也是O(\log_{2}n)。以a^{11}为例,说明:

先把a^{11}分解成a^{8}a^{2}a^{1}的乘积,而 a^{1}*a^{1}=a^{2}a^{2}*a^{2}=a^{4}a^{4}*a^{4}=a^{8},都是2的倍数,逐级递推就可。那么如何将 n 分解成 11=8+2+1 这样的倍乘关系?用二进制就可解决:把 n 转换为二进制数,二进制数中每一位的权值都是低一位的两倍,对应的a^{i}是倍乘的关系。另外,还有一个需要处理的问题:如何跳过那些不需要的?这里只需要判断二进制位是否为0.(0就是需要跳过的),利用二进制的位运算是很容易实现:

  1. (n&1)==1,取 n 的最后一位,并且判断这一位是否需要跳过

  2. n>>=1,把 n 右移一位,目的是把刚处理过的 n 的最后一位去掉

java代码如下:

int fastPow(int a, int n){
        int base = a;    //直接用a计算也行
        int res = 1;    //用res返回结果
        while (n!=0){
            if((n&1)==1){    //如果n的最后一位是1,表示这个地方需要乘
                res *= base;
            }
            base *= base;    //推算乘积:a^2 -> a^4 -> a^8 ...
            n >>= 1;    //n右移一位,把刚处理过的n的最后一位去掉
        }
        return res;
    }
执行步骤
 nres(res *= base)base(base *= base)
第1轮1011a^{1}a^{2}
第2轮101a^{1}*a^{2}a^{4}
第3轮10是0,res不变a^{8}
第4轮1a^{1}*a^{2}*a^{8}a^{16}
结束0  

 

 

 

 

 

 

 

快速幂取模

性质:a^{n}\ mod\ m = (a\ mod\ m)^{n}\ mod\ m

矩阵快速幂

给定一个 m*m 的矩阵 A,求它的 n 次幂 A^{n}

算法:将矩阵当作变量来操作。首先要定义矩阵的结构体,并且定义矩阵相乘的操作

java代码如下:

    final int MAXN = 2;    //矩阵的阶
    final int MOD = (int)1e4;    //根据题目定义的模

    class Matrix{    //定义矩阵
        int[][] p;
        public Matrix(){
            this.p = new int[MAXN][MAXN];
        }
    }

    Matrix Multi(Matrix a, Matrix b){    //矩阵的乘法
        Matrix res = new Matrix();
        for(int i=0; i<MAXN; i++)
            for (int j=0; j<MAXN; j++)
                for(int k=0; k<MAXN; k++){
                    res.p[i][j] = (res.p[i][j] + a.p[i][k] * b.p[k][j]) % MOD;
                }
        return res;
    }

下面是矩阵快速幂的java代码(复杂度:矩阵乘法复杂度:O(m^{3}),快速幂的复杂度:O(\log_{2}n),所以总复杂度为:O(m^{3}\log_{2}n)):

Matrix fastm(Matrix a, int n){
        Matrix res = new Matrix();
        for(int i=0; i<MAXN; i++)    //初始化为单位矩阵,相当于前面所说的“int res=1;”
            res.p[i][i] = 1;
        while (n>0){
            if((n&1)!=0)
                res = Multi(res,a);
            a = Multi(a,a);
            n >>= 1;
        }
        return res;
    }

最大公约数 GCD

整数 a 和 b 的最大公约数记为:gcd(a,b)

经典的欧几里得算法,代码如下:

int gcd(int a,int b){
        return b == 0 ? a : gcd(b,a%b);
    }

最小公倍数 LCM

整数 a 和 b 的最小公倍数记为:lcm(a,b)

代码如下:

int lcm(int a, int b){
        return a/gcd(a,b)*b;
    }

扩展欧几里得算法与二元一次方程的整数解

问题:给出整数a、b、n,问方程 ax+by=n 什么时候有整数解?如何求所有的整数解?

有解的充分必要条件:gcd(a,b) 可以整除 n。简单解释如下:

令 a=gcd(a,b)a'、b=gcd(a,b)b',有 ax+by=gcd(a,b)(a'x+b'y)=n;如果x、y、a'、b'都是整数,那么 n 必须是 gcd(a,b) 的倍数才有整数解

如果确定有解,一种解题方法是先找到一个解(x0, y0),那么通解公式如下:

x = x0 + bt,

y = y0 - at,     t 是任意整数

所以,问题转化为如何求 (x0,y0)。利用扩展欧几里得算法可以求出这个特解

扩展欧几里得算法

当方程符合 ax + by = gcd(a,b) 时,可以用扩展欧几里得算法求 (x0,y0). java代码如下:

    int x,y;    //x,y作为成员变量,通过以下方法即可得到一个特解(x0,y0)
    void extend_gcd(int a,int b){
        if(b==0){
            x=1;
            y=0;
            return;
        }
        extend_gcd(b,a%b);
        int temp = x;
        x = y;
        y = temp - (a/b) * y;
    }

程序运行理解可如下:

求任意方程 ax+by=n 的一个整数解

步骤如下:

  1. 判断方程 ax+by=n 是否有整数解,有解的条件是 gcd(a,b) 可以整除 n
  2. 用扩展欧几里得算法求 ax+by=gcd(a,b) 的一个解 (x0,y0)
  3. 在 ax+by=gcd(a,b) 两边同时乘以 n/gcd(a,b),得: ax_{0}n/gcd(a,b)\ +\ by_{0}n/gcd(a,b)=n
  4. 对照 ax+by=n,得到它的一个解(x0',y0')是:x'_{0}=x_{0}n/gcd(a,b) \ \ \ y'_{0} =y_{0}n/gcd(a,b)

扩展欧几里得算法应用场合

  1. 求解不定方程
  2. 求解模的逆元
  3. 求解同余方程

同余与逆元

同余

两个整数 a、b 和一个正整数 m,如果 a 除以 m 所得的余数和 b 除以 m 所得的余数相等,即 a mod m = b mod m,称 a 和 b 对于 m 同余,m 称为同余的模。也可以这样理解:m|(a-b),即 a-b 是 m 的整数倍。

同余符号即为: a\equiv b(mod\ m)

一元线性同余方程

 ax\equiv b(mod\ m),即 ax 除以 m,b 除以 m,两者余数相同,这里 a、b、m 都是整数,求解 x 的值

方程也可以这样理解:ax-b 是 m 的整数倍。设 y 是倍数,那么 ax-b=my,移项得到 ax-my=b。因为 y 可以是负数,改写成 ax+my=b,这就是在扩展欧几里得算法中提到的二元一次不定方程

当且仅当 gcd(a,m)能整除 b 时有整数解。

当 gcd(a,m)=b 时,可以直接用扩展欧几里得算法求解 ax+my=b

如果不满足gcd(a,m)=b,还能用扩展欧几里得算法求解 ax+my=b 吗?答案是肯定的,但是需要结合下面的逆元

逆元

给出 a 和 m,求解方程 ax\equiv 1(mod\ m),即 ax 除以 m 的余数是 1

该问题等价于求解 ax+my=1,可以用扩展欧几里得算法求解。

方程 ax\equiv 1(mod\ m) 的一个解 x,称 x 为 a 模 m 的逆

求逆元的代码如下:

int x,y;    //结合上述的扩展欧几里得算法 extend_gxd
int mod_inverse(int a,int m){
        extend_gcd(a,m);
        return (m + x%m) % m;    //x 可能是负数,需要处理
    }

逆元与除法取模

见上述模运算中的除法

逆元与求解二元一次方程 ax+my=b

如果得到了 a 的逆,可以来求解形如 ax\equiv b(mod\ m) 的任何同余方程。方法如下:

令 a' 是 a 模 m 的逆,则 a'a\equiv 1(mod\ m);在ax\equiv b(mod\ m) 的两边同时乘以 a',得到 a'ax\equiv a'b(mod\ m),所以 x\equiv a'b(mod\ m)

理解:因为 a' 是 a 的逆元,由模运算的除法性质可得 (ax/a)mod\ m = aa'x\ mod\ m = x\ mod\ m

利用逆元求解二元一次方程:

利用逆元的求解方法
步骤

求解方程 ax+my=b

同余方程是 ax\equiv b(mod\ m)

例:求解 8x+31y=24

同余方程是8x\equiv 24(mod\ 31)

1有解的条件:gcd(a,m)能整除 bgcd(8,31)=1 能整除 24
2ax\equiv 1(mod\ m)的逆元 a',等价于用扩展欧几里得算法求解ax+my=18x+31y=1 的一个解是 x=4,y=-1 即一个逆元是 a'=4
3一个特解是 x=a'bx=a'b=4*24=96
4代入方程 ax+my=b,求解 y代入8x+31y=24,得到 y = -24

 

 

 

 

 

 

 

 

 

对于 x=a'b 的理解:\because aa'\equiv 1(mod\ m) ,两边同乘b,aa'b\equiv b(mod\ m),又 ax\equiv b(mod\ m) ,对比得 \therefore x= a'b

素数

试除法判断素数

给定 n,如果它不能整除 \left [ 2 \right,\sqrt{n} ] 内的所有数,它就是素数。证明如下:

设 n=a*b ,有 min(a,b)\leq \sqrt{n},  令 a\leq b 。只要检查\left [ 2 \right,\sqrt{n} ] 内的数,如果 n 不是素数,就能找到一个 a。如果不存在这个 a,那么 (\sqrt{n},\ n-1] 内也不存在 b

以上判断的范围可以再缩小:[2,\ \sqrt{n}] 内的所有素数

用试除法判断素数,复杂度是 O(\sqrt{n}),对于 n\leq 10^{12} 的数是没有问题的

java代码如下:

 boolean is_prime(int n){
        if(n<=1) return false;
        for(int i=2; i*i<=n; i++){    //比这样写更好:for(int i=2;i<=Math.sqrt(n);i++)
            if(n % i == 0)
                return false;
        }
        return true;
    }

用埃式筛法求素数的数量

一个与素数有关的问题:求 [2,n] 内所有的素数

对于初始队列 {2,3,4,5,6,7,8,9,10,11,...,n},操作步骤如下:

  1. 输出最小的素数2,然后筛掉 2 的倍数,剩下 {3,5,7,9,11,...}
  2. 输出最小的素数3,然后筛掉 3 的倍数,剩下 {5,7,11,13,...}
  3. 输出最小的素数5,然后筛掉 5 的倍数,剩下 {7,11,13,...}

继续以上步骤,直到队列为空

下面是java程序,其中 visit[i]=true,表示它被筛掉了,不是素数

    final int MAX = (int)1e7;    //定义空间大小
    int[] prime = new int[MAX];    //存放素数,记录 visit[i]=false 的项
    boolean[] visit = new boolean[MAX];    //true 表示被筛掉,不是素数
    int E_sieve(int n){    //埃式筛法,计算 [2,n] 内的素数
        for(int i=0;i<=n;i++)
            visit[i] = false;    //初始化
        for (int i=2;i*i<=n;i++)    //筛掉非素数
            if(!visit[i])
                for (int j=i*i;j<=n;j+=i)
                    visit[j] = true;
        int k=0;    //统计素数的个数
        for (int i=2;i<=n;i++)
            if(!visit[i])
                prime[k++] = i;    //存储素数
        return k;
    }

埃式筛法应用于大区间素数

如果把 [2,n] 看成一个区间,那么可以把埃式筛法扩展到求区间 [a,b] 的素数,a< b\leq 10^{12}b-a\leq 10^{6}

根据埃式筛法很容易理解试除法判断素数的原理:2\sim \sqrt{n} 内的非素数 b 肯定对应一个比它小的素数 a。在用试除法的时候,如果 n 能整除 a,已经证明了 n 不是素数,b 就不用再试了

这个原理可以用来理解大区间求素数问题。先用埃式筛法求 [2,\sqrt{b}] 内的素数,然后用这些素数来筛 [a,b] 区间的素数即可

  1. 计算复杂度:O(\sqrt{b}\log \log \sqrt{b})+O((b-a)\sqrt{b-a})
  2. 空间复杂度:需要定义两个数组,一个用于处理 [2,\sqrt{b}] 内的素数,另一个用于处理 [a,b] 内的素数,空间复杂度为:O(\sqrt{b})+O(b-a)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值