快速幂算法

题目:

输入b,p,k 的值,求b^p mod k 的值。其中 2≤b,p,k≤10^9。 

看到题目时,大多数人第一时间想到的肯定是直接求出b^p再算b^p%k,但是这种方法只适用于b、p、k较小时,当b、p、k过大时必将出现数据溢出、超时等现象,导致错误。

那么,这种题型到底该怎么办呢?

首先要知道取模运算的运算法则:

  1. (a + b)%p=(a%p + b%p)%p
  2. (a -  b)%p=(a%p - b%p)%p
  3. (a * b)%p=(a%p * b%p)%p
  4. (a ^ b)%p=((a%p)^b)%p

可以发现多个因数相乘再取模等同于每个因数先取模再相乘。

而b^p表示p个b相乘,在这里,我们可以利用规则三设计一个循环程序循环p次,每次使用一个b,在每次运算时让每个因数先对k取模,再相乘进行解题。

代码如下:

#include 
int main(int argc, char* argv[])
{
    long long b = 0, p = 0, k = 0;
    scanf("%lld%lld%lld", &b, &p, &k);
    long long tem = 1;
    long long i = 0;
    for (i = 0; i < p; i++)
    {
        tem = tem * (b % k);
        tem %= k;
    }
    printf("%lld\n", tem );
    // 请在此输入您的代码
    return 0;
}

 猛地一看不细究的话,这个代码还是可以的,但是细细一想就会发现:无论p为多少,程序就会循环多少次,那么当p很大时,程序运行时间也会变长,程序的效率不太行,还要就行优化。

那么该如何优化呢?

我们知道在数学中有a^b=(a^2)^b/2=(a*a)^b/2(b为偶数)、a^b=a^(b-1)*a=(a*a)^(b-1)/2*a(b为奇数)这样的降幂操作

以3^10为例:

第一次降幂:3^10=(3*3)^5=9^5

此时指数由10缩减一半变成了5,而底数变成了原来的平方,求3^10原本需要执行10次循环操作,求9^5却只需要执行5次循环操作,但是3^10却等于9^5,我们用一次降幂的操作减少了原本一半的循环量,特别是在幂特别大的时候效果非常好,例如2^10000=4^5000,底数只是做了一个小小的平方操作,而指数就从10000变成了5000,减少了5000次的循环操作。

现在的问题是如何把指数5变成原来的一半,5是一个奇数,5的一半是2.5,但是我们不能让指数为小数,这样反而会变得更复杂,因此我们不能这么简单粗暴的直接执行5/2,应该先取出一个底数单独相乘

9^5=(9^4)*(9^1)

此时我们抽出了一个底数9,这里即为9^1,这个9^1我们先单独移出来,剩下的9^4又能够在执行“降幂”操作了,把指数缩小一半,底数执行平方操作

第二次降幂:9^5=(81^2)*(9^1)

第三次降幂:(81^2)*(9^1)=(6561^1)*(9^1)

此时,我们发现指数又变成了奇数,按照上面对指数为奇数的操作方法,应该抽出了一个底数6561,这里即为6561^1,此时指数变成了0,也就意味着我们无法再进行“降幂”操作了。

(6561^0)*(9^1)*(6561^1)=1*(9^1)*(6561^1)=(9^1)*(6561^1)=9*6561=59049

可以发现降幂操作可以极大地节省程序运行的时间,提高效率。
代码如下:

int main(int argc, char* argv[])
{
    long long b = 0, p = 0, k = 0;
    scanf("%lld%lld%lld", &b, &p, &k);
    long long tem = 1;
    while (p)
    {
        if (p % 2 == 1)//指数是奇数时取出一个b
        {
            //tem = tem * b % k;
            tem = (tem % k * b % k) % k;
        }
        p /= 2;//指数减半
        //b=b*b%k;//底数乘方再取模
        b = (b % k * b % k) % k;
    }
    printf("%lld\n", tem);
    // 请在此输入您的代码
    return 0;
}

这个代码可以说是很好了,但是能不能对它进行再一次优化呢?

答案是:当然可以。

想要对其进行进一步优化,就要用到位操作符:“ & ”和“ >> ”。

位操作符我在二进制中1的个数一文中有过解释,在这里就不再赘述,直接上优化后的代码:

#include 
int main(int argc, char* argv[])
{
    long long b = 0, p = 0, k = 0;
    scanf("%lld%lld%lld", &b, &p, &k);
    long long tem = 1;
    while (p)
    {
        if (p & 1)//指数是奇数时取出一个b
        {
            //tem = tem * b % k;
            tem = (tem % k * b % k) % k;
        }
        p >>= 1;//指数减半
        //b=b*b%k;//底数乘方再取模
        b = (b % k * b % k) % k;
    }
    printf("%lld", tem);
    // 请在此输入您的代码
    return 0;
}

你可能感兴趣的:(算法,数学建模)