zoukankan      html  css  js  c++  java
  • 高速幂取余算法

    以下是一个高速幂的介绍:

    先贴一个秦九韶算法(Horner算法)的原理:

    设有n+1项的n次函数

    f(x)=a_nx^n+a_{n-1}x^{n-1}+a_{n-2}x^{n-2}+......+a_2x^2+a_1x+a_0


    将前n项提取公因子x,得

    f(x)=(a_nx^{n-1}+a_{n-1}x^{n-2}+a_{n-2}x^{n-3}+......+a_2x+a_1)x+a_0


    再将括号内的前n-1项提取公因子x。得

    f(x)=((a_nx^{n-2}+a_{n-1}x^{n-3}+a_{n-2}x^{n-4}+......+a_2)x+a_1)x+a_0


    如此重复提取公因子x,最后将函数化为

    f(x)=(((a_nx+a_{n-1})x+a_{n-2})x+......+a_1)x+a_0


    f_1=a_nx+a_{n-1}

    f_2=f_1x+a_{n-2}

    f_3=f_2x+a_{n-3}

    ......

    f_n=f_{n-1}x+a_0


    f_n即为所求

    以下是解说高速幂的:(By  夜せ︱深   感谢作者)

    高速幂取模算法

    在站点上一直没有找到有关于高速幂算法的一个具体的描写叙述和解释,这里,我给出高速幂算法的完整解释,用的是C语言。不同语言的读者仅仅好换个位啦,毕竟读C的人较多~

    所谓的高速幂。实际上是高速幂取模的缩写,简单的说,就是高速的求一个幂式的模()。在程序设计过程中。常常要去求一些大数对于某个数的余数。为了得到更快、计算范围更大的算法,产生了高速幂取模算法。

    [有读者反映在讲高速幂部分时有点含糊,所以在这里对本文进行了改动,作了更具体的补充。争取让很多其它的读者一目了然]

    我们先从简单的样例入手:求a^b % c = ?

    算法1.首先直接地来设计这个算法:

    int ans = 1;

    for(int i = 1;i<=b;i++)

    {

    ans = ans * a;

    }

    ans = ans % c;

    这个算法的时间复杂度体如今for循环中,为Ob.这个算法存在着明显的问题。假设ab过大,非常easy就会溢出。

    那么,我们先来看看第一个改进方案:在讲这个方法之前,要先有这样一个公式:a^b%c=(a%c)^b%c.这个公式大家在离散数学或者数论其中应该学过。只是这里为了方便大家的阅读,还是给出证明:

    引理1:a^b%c = (a%c)^b%c

     

    上面公式为以下公式的引理,即积的取余等于取余的积的取余。

     

    证明了以上的公式以后,我们能够先让a关于c取余,这样能够大大降低a的大小,

    于是不用思考的进行了改进:

    算法2

    int ans = 1;

    a = a % c; //加上这一句

    for(int i = 1;i<=b;i++)

    {

    ans = ans * a;

    }

    ans = ans % c;

    聪明的读者应该能够想到,既然某个因子取余之后相乘再取余保持余数不变,那么新算得的ans也能够进行取余,所以得到比較良好的改进版本号。

    算法3

    int ans = 1;

    a = a % c; //加上这一句

    for(int i = 1;i<=b;i++)

    {

    ans = (ans * a) % c;//这里再取了一次余

     

    }

    ans = ans % c;

    这个算法在时间复杂度上没有改进,仍为O(b),只是已经好非常多的。可是在c过大的条件下,还是非常有可能超时,所以。我们推出下面的高速幂算法。

    高速幂算法依赖于下面明显的公式,我就不证明了。

     

    那么我们能够得到下面算法:

    算法4

    int ans = 1;

    a = a % c;

    if(b%2==1)

    ans = (ans * a) mod c; //假设是奇数,要多求一步,能够提前算到ans

    k = (a*a) % c; //我们取a2而不是a

    for(int i = 1;i<=b/2;i++)

    {

    ans = (ans * k) % c;

    }

    ans = ans % c;

     

    我们能够看到,我们把时间复杂度变成了O(b/2).当然,这样子治标不治本。

    但我们能够看到,当我们令k = (a * a) mod c时,状态已经发生了变化,我们所要求的终于结果即为(k)b/2 mod c而不是原来的ab mod c所以我们发现这个过程是能够迭代下去的。

    当然,对于奇数的情形会多出一项a mod c,所以为了完毕迭代,当b是奇数时,我们通过

    ans = (ans * a) % c;来弥补多出来的这一项。此时剩余的部分就能够进行迭代了。

     

    形如上式的迭代下去后。当b=0时,全部的因子都已经相乘。算法结束。于是便能够在Olog b的时间内完毕了。于是。有了终于的算法:高速幂算法。

    算法5:高速幂算法

     

    int ans = 1;

    a = a % c;

    while(b>0)

    {

     

    if(b % 2 == 1)

    ans = (ans * a) % c;

    b = b/2;

    a = (a * a) % c;

    }

    将上述的代码结构化。也就是写成函数:

    int PowerMod(int a, int b, int c)

    {

    int ans = 1;

    a = a % c;

    while(b>0)

    {

     

    if(b % 2 = = 1)

    ans = (ans * a) % c;

    b = b/2;

    a = (a * a) % c;

    }

    return ans;

    }

    本算法的时间复杂度为Ologb),能在差点儿全部的程序设计(竞赛)过程中通过,是眼下最经常使用的算法之中的一个。

    下面内容仅供參考:

    扩展:有关于高速幂的算法的推导,还能够从还有一个角度来想。

    =? 求解这个问题,我们也能够从进制转换来考虑:

    10进制的b转化成2进制的表达式:

    注意此处的要么为0。要么为1,假设某一项,那么这一项就是1,这个相应了上面算法过程中b是偶数的情况,为1相应了b是奇数的情况[不要搞反了。读者自己好好分析,能够联系10进制转2进制的方法],我们从依次乘到。对于每一项的计算,计算后一项的结果时用前一项的结果的平方取余。对于要求的结果而言,为时ans不用把它乘起来,[由于这一项值为1],为1项时要乘以此项再取余。这个算法和上面的算法在本质上是一样的。读者能够自行分析,这里我说不多说了,希望本文有助于读者掌握高速幂算法的知识点。当然。要真正的掌握,不多练习是不行的。

  • 相关阅读:
    【BZOJ1495】[NOI2006]网络收费 暴力+DP
    【BZOJ2827】千山鸟飞绝 hash+堆+SBT
    【BZOJ2905】背单词 fail树+DFS序+线段树
    【BZOJ3120】Line 矩阵乘法
    【BZOJ1441】Min 拓展裴蜀定理
    【BZOJ3195】[Jxoi2012]奇怪的道路 状压DP
    【BZOJ3416】Poi2013 Take-out 栈
    【BZOJ4244】邮戳拉力赛 DP
    【BZOJ3717】[PA2014]Pakowanie 状压DP
    【BZOJ1217】[HNOI2003]消防局的设立 树形DP
  • 原文地址:https://www.cnblogs.com/gccbuaa/p/6937145.html
Copyright © 2011-2022 走看看