题目:
输入b,p,k 的值,求b^p mod k 的值。其中 2≤b,p,k≤10^9。
看到题目时,大多数人第一时间想到的肯定是直接求出b^p再算b^p%k,但是这种方法只适用于b、p、k较小时,当b、p、k过大时必将出现数据溢出、超时等现象,导致错误。
那么,这种题型到底该怎么办呢?
首先要知道取模运算的运算法则:
- (a + b)%p=(a%p + b%p)%p
- (a - b)%p=(a%p - b%p)%p
- (a * b)%p=(a%p * b%p)%p
- (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;
}