zoukankan      html  css  js  c++  java
  • 快速幂算法笔记

    快速幂定义: 1.快速幂就是快速算底数的n次幂。其时间复杂度为 O(log₂N), 与朴素的O(N)相比效率有了极大的提高。用法:用于求解 a 的 b 次方,而b是一个非常大的数,用O(n)的复杂度会超时。那么就需要这个算法,注意它不但可以对数求次幂,而且可用于矩阵快速幂。--百度百科

    2.所谓的快速幂,实际上是快速幂取模的缩写,简单的说,就是快速的求一个幂式的模(余)。在程序设计过程中,经常要去求一些大数对于某个数的余数,为了得到更快、计算范围更大的算法,产生了快速幂取模算法  --http://blog.csdn.net/xuruoxin/article/details/8578992

     

    模运算规则:
    模运算与基本四则运算有些相似,但是除法例外。其规则如下:
    (a + b) % p = (a % p + b % p) % p
    (a – b) % p = (a % p – b % p) % p
    (a * b) % p = (a % p * b % p) % p
    ab % p = ((a % p)b) % p
    结合率:
    ((a+b) % p + c) % p = (a + (b+c) % p) % p
    ((a*b) % p * c)% p = (a * (b*c) % p) % p

    可能你会问了这个算法有什么用呢?其实用的更多是使用矩阵快速幂,算递推式,注意是递推式,简单的如斐波那契数列的第一亿项的结果模上10000000后是多少你还能用递推式去,逐项递推吗?当然不能,这里就可以发挥矩阵快速幂的神威了,那斐波那契数列和矩阵快速幂能有一毛钱的关系?答案是有而且很大。

    a的b次方可以用pow(a, b)是数学头文件math.h里面有的函数

    但是返回值是double类型 + 数据有精度误差···那么自己写for循环吧

    1 LL pow(LL a, LL b){    //a的b次方
    2     LL ret = 1;
    3     for(LL i = 1; i <= b; i ++){
    4         ret *= a;
    5     }
    6     return ret;
    7 }

    若题目b的范围是1 <= b <= 1e9···那么会超时···

    看个例子

    比如计算

    2*2*2*2*2*2*2*2*2*2*2

    可以这样算

    原式=4*4*4*4*4*2

    =8*8*4*2

    =16*4*2

    你看,相同的可以先合并,减少计算步骤

    a^b mod c = (a mod c)^c mod c 

    如果题目说数据很大,还需要求余,那么递归代码就可以这么写:/ / 有的时候题目要求求余,没要求就不用求余数了

    1 LL pow_mod(LL a, LL b){  //a的b次方
    2     if(b == 0) return 1;
    3     LL ret = pow_mod(a, b/2);
    4     ret = ret * ret % MOD;
    5     if(b % 2 == 1) ret = ret * a % MOD;
    6     return ret;
    7 }

    递推

     1 LL pow_mod(LL a, LL b){ //a的b次方
     2     LL ret = 1;
     3     while(b != 0){
     4         if(b % 2 == 1){
     5             ret = (ret * a) % MOD ;
     6         }
     7         a = (a * a ) % MOD ;
     8         b /= 2;
     9     }
    10     return ret;
    11 }

    位运算形式:(速度又快,又好理解,在加一个求余p

    1 LL pow_mod(LL a, LL b, LL p){//a的b次方求余p 
    2     LL ret = 1;
    3     while(b){
    4         if(b & 1) ret = (ret * a) % p;
    5         a = (a * a) % p;
    6         b >>= 1;
    7     }
    8     return ret;
    9 }

    学习Rabin-Miller算法的时候我们为了防止求a*b mod{m}的时候溢出,通常会使用一种叫做“快速乘”的算法。不得不承认我第一次见到这东西的时候确实是一脸懵逼的,下面我们来用之前的知识学习一下这个算法。

    (请先忘记a*b = b*a

    现在我们的任务是:

    用O(log(b))次加法计算a*b%mod(因为直接计算a*b会溢出,而我们保证加法不会溢出)
    

    例如:1e17*1e17 mod 1e18

    注意到一个小学生级别的事实:a*b = underbrace{a+cdots+a}_b

    所以我们可以把a*b看成a的b次幂

    现在我们check一下这个新的“幂运算”是否满足上面提到的3个条件

    1. a+a in mathbb{Z}封闭性显然满足
    2. (a+b)+c = a+(b+c)结合律满足
    3. 单位元为0

    全部都满足呢!Y(^o^)Y

    所以我们把上面的代码中的乘法改成加法就大功告成啦(当然单位元也是要改的233)!

    有了快速幂,于是,快速乘诞生了:

     1 ll fastMultiplication(ll a,ll b,ll mod){
     2     ll ans = 0;
     3     while(b){
     4         if(b%2==1){
     5             b--; 
     6             ans = ans + a;
     7                         ans %= mod;
     8         }else{
     9                         b /= 2;
    10             a = a + a;
    11                         a %= mod;
    12         }
    13     }
    14     return ans;
    15 }
    1 LL mul(LL a, LL b, LL p){//快速乘,计算a*b%p 
    2     LL ret = 0;
    3     while(b){
    4         if(b & 1) ret = (ret + a) % p;
    5         a = (a + a) % p;
    6         b >>= 1;
    7     }
    8     return ret;
    9 }
     1 ll fastExp(ll a,ll k){ //快速幂2
     2     ll ans = 1;
     3     while(k){
     4         if(k%2==1){
     5             k--; 
     6             ans = ans * a;
     7         }else{
     8             a = a * a;
     9             k/=2;
    10         }
    11     }
    12     return ans;
    13 }

    快速幂用到了什么性质

    1. “乘法”满足封闭性
    2. “乘法”满足结合律
    3. “乘法”下有单位元(其实这条不一定要,因为可以初始化ans到a(不过anyway我们假设一开始是单位元吧
       1 #include <iostream>//实例代码
       2 using namespace std;
       3 int quickPow(int a,int b){   //快速幂
       4     int ans = 1;
       5     while(b){
       6         if(b&1) ans*=a;
       7         a *= a;
       8         b /= 2;
       9     }
      10     return ans;
      11 }
      12 int pow(int a,int b){   //普通乘
      13     int ans = 1;
      14     for(int i=1; i<=b; i++)
      15         ans *= a;
      16     return ans;
      17 }
      18 int main(int argc, const char * argv[]) {
      19     cout<<quickPow(2, 20)<<endl;   //第一种方式
      20     cout<<pow(2,20)<<endl;    //第二种方式
      21     return 0;
      22 }
      23 
      24 作者:徐森威
      25 链接:http://www.jianshu.com/p/1c3f88f63dec
      26 來源:简书
      27 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
       while(b){    //快速幂核心代码
            if(b&1) ans*=a;
            a *= a;
            b /= 2;
       }

      上面的两种方式,一种复杂度是O(logn),一种复杂度是O(n)。当你数量级很大的时候,比如10000000000时,这个复杂度的时间差别就很大了

      怎么得到这个快速幂的代码的呢?我们假设需要求274

      74转化为二进制为1001010,根据二进制对应位的1,可以把74拆分为64+8+2 ,所以
      74 = 26 + 23 + 21
      274 = 226 + 223 + 221

       while(b){    //快速幂核心代码
            if(b&1) ans*=a;
            a *= a;
            b /= 2;
       }

      下面看一下上面这段快速幂程序中的ans和a的用法 (74二进制1001010为从右到左下面表格从1-7)

       初始值01(ans变了)01(ans变了)001(ans变了)
      a = 2n 21 22 24 28 216 232 264 2128
      ans 20 20 20×22 20×22 20×22×28 20×22×28 20×22×28 20×22×28×264

      这里有两个变量,第一个变量a默认用来存2n,,变量ans存储的是最后的结果,当遇到第k位的二进制位是1的时候,则ans ×= 2k

      举个例子,74的二进制表示是1001010,因为是除2运算,所以从右往左遍历,到右边第二个时,ans ×= 22,到右边第四个时,ans ×= 28,到右边第7个时,ans ×= 264,最后ans = 274,至于ans乘的过程中的那些22、28、264怎么来?a变量不是一直在不断增加吗,ans乘的就是a变量对应位的值,对应上面的表格可能看的更清楚一点

  • 相关阅读:
    5.2 HTML5表单与PHP交互
    5.1 HTML5表单的创建
    4.3 HTML5布局的使用
    MATLAB矩阵——2.5稀疏矩阵
    MATLAB矩阵——2.4矩阵的特征值与特征向量
    MATLAB矩阵——2.3矩阵求值
    MATLAB矩阵处理——2.2矩阵变换
    MATLAB矩阵处理——2.1特殊矩阵
    MATLAB基础知识——1.6基本运算
    MATLAB基础知识——1.5矩阵元素的引用
  • 原文地址:https://www.cnblogs.com/Roni-i/p/7163565.html
Copyright © 2011-2022 走看看