透彻理解快速幂运算&取模乘法性质(附核心代码模板)

快速幂运算&取模乘法性质

相关知识

1、问题引入

  输入x,n,p,如何计算xn mod p?
  一种暴力的做法就是直接将n个x乘起来,最终mod p。理论上来说,这样做显然是可以的,但是很明显,这样做的话,程序要循环n次,也就是说它的时间复杂度是O(n),如果n非常大,就很可能会导致TLE。那有什么办法,可以提高运算效率呢?

2、快速幂思想

在这里插入图片描述
​  如上图所示,快速幂的本质就是:底数不断取2次幂,指数不断除2次幂,直到指数除到为1,计算完毕。
  ​这样做有什么好处?我们说过,原先的n次幂运算,它的时间复杂度为O(n),而如果是快速幂运算,如上图所示,时间复杂度就被降到了O(log2n)。
  对于一个快速幂只要循环100次的低时间复杂度的运算,对于暴力的数学幂运算,究竟要循环多少次?下图给出了答案。
在这里插入图片描述
​  1022亿次循环,远远比100次多。快速幂,强行将10的22亿次降到100次,这就是快速幂的魅力。

​  因此,在题目中出现幂运算的时候并且指数范围非常大的时候,我们应该要联想到快速幂,来以防TLE导致的罚时

3、快速幂中的奇数幂特判

​  快速幂的思路其实很简单,就是底数不断取2次幂,指数不断除2次幂,直到指数除到为1,计算完毕。但是,这个过程中,如果指数的奇数的话,就没有办法除2次幂。对于整数的除,都是整除。例如求2的5次方,经过一次快速幂,它并不会变成4的2.5次方,而是4的2次方,因为4整除2等于2。跟实际还是差了一个4的0.5次方,也就是“原底数”。
  每一次到奇数幂时,就会有这些原底数被漏乘,最终形成一群因为是奇数幂而漏乘的“底数群”。因此,我们需要对奇数次幂的情况进行特判,存储这些没乘上的底数群,最终return的时候将它们都乘上。

​  由上,引入一个每一次处理奇数次幂时,未被底数乘上的待乘底数群。这个底数群的初始值为1,当为奇数次幂时,这个未乘的底数群就以乘以目前的底数的形式进行更新。最后,这个未乘的底数群幂为1时不包含奇数幂时遗漏的底数的不完全结果,就是快速幂的最终结果,思路如下所示:

  我们的思路可以进一步简化为:

4、快速幂背后的一些问题

  快速幂思想固然好,但是,快速幂带来的问题也存在,即底数数字变大的速度也是指数爆炸的速度
​  例如,我们要求999910000,那么快速幂的过程就是这样的:
在这里插图片描述
  短短4次循环内,底数就到了100次幂!而就算是再后面的循环,数据有多大可想而知。那有什么办法,可以使得我们的数据缩小呢?
​  一般题目中考虑到这些问题,都会让我们将最终结果取某一个大数的模。
​  因此都会有类似于这样一句话:“由于数字可能比较大,输出数字一律取XXXXXXX的模。”
​  或者只取“个位”之类的限制性话语(言外之意就是所有数据都对10取余)。
​  例如,在2021年湖南大学的ACM新生赛中,我们可以看到:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XjKv5tUi-1646105006794)(C:\Users\49593\AppData\Roaming\Typora\typora-user-images\image-20220227201213043.png)]](https://img-blog.csdnimg.cn/a497a8b3b7a640a9b5f03ba04c4be6cb.png​  即便是这样,还是存在着一些问题。我们的思路是对最终输出数字取模,以控制最终输出数据的大小。但是在没达到最终数字之前,我们的数据就很可能已经爆了。以求99999910000为例:
在这里插入图片描述​  那么,怎么在过程中怎么控制数据呢,换言之,怎么在确保最终输出结果不变的情况下,让过程中的运算数据变小呢?可能有的同学会想到,对每一层循环的结果取模,这个思路很好,我们后续也会用到,不过这还是不够的,即便是这样还是有可能爆掉。还是以上图为例,第一次循环是107,对它取109的模(998244353)还是不会变,那么第二次循环它还是来到了1023,还是爆数据了。显然,这种方法还不够完善。

​  快速幂中,应付数据范围问题的终极秘密武器,就是“取模运算的乘法法则”

5、取模运算乘法法则的介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XrU2aPs9-1646105006796)(C:\Users\49593\AppData\Roaming\Typora\typora-user-images\image-20220227203533465.png)]
​  如上图所示,这是取模运算的乘法法则。可能有的同学会疑惑,这乘法法则和我们快速幂运算中的幂运算有什么关系呢?其实我们都知道,幂运算的本质还是乘运算,快速幂中的幂,永远都是二次幂的迭代,如下图所示:

​  刚才我们最开始想到的是,对每一层循环的结果取余,即如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IH6Q0EKK-1646105006797)(C:\Users\49593\AppData\Roaming\Typora\typora-user-images\image-20220227205357307.png)]
​  现在,利用取模运算性质,这就变成了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DjheNB1J-1646105006797)(C:\Users\49593\AppData\Roaming\Typora\typora-user-images\image-20220227210114141.png)]
​  有了快速幂控制时间,再加以取模性质控制大小,幂运算的两个最大困难,就被成功克服了。

6、快速幂运算&取模乘法性质の核心代码模板(求x的n次幂对p取模的结果)

// 求“x的n次幂并最终结果对p取模”的快速幂代码模板
typedef ll long long
ll quick_pow(ll x, ll n) //利用快速幂算法+取模运算性质,剧减数据范围与时间复杂度
{
   ll lack=1; //lack代表每一次奇数幂时积累下的未乘的底数群,所以初始值为1
   x=x%p; //先将底数mod p,缩小数据范围
   while(n!=1)//当指数为1时,底数x就是最终结果,不过还没乘入奇数次幂时的底数群
   {
       if(n&1)lack=lack*(x%p);  //n&1即n为奇数时,lack存储,由于奇数幂被整除,少乘的底数群
       x=((x%p)*(x%p))%p; //利用取模乘法性质: (a*b)%p=((a%p)*(b%p))%p
       n>>=1; //幂的二进制位向右移一位,即幂整除2,这样整除效率更高
   }
   return ((lack%p)*(x%p))%p; //奇数次幂时未乘的底数群,乘上只考虑偶数次幂时的底数结果,就是结果
}  

相关题目

1、N 的 N 次幂的个位数

参考思路

​  看到只求个位数,其实就是暗示所有结果都mod10,那么就可能用到我们的乘法的取模运算法则;看到N的范围109,那么显然暴力幂的方式肯定不太行,而要用快速幂,并且还需要借助mod10这个先决条件先**把数据范围给降下来。**那么思路就有了:快速幂+取模乘法性质一条龙解题。

参考AC代码
#include<bits/stdc++.h>
using namespace std;
#typedef ll long long
ll Pow(ll x, ll n) //利用快速幂算法+取模运算性质,剧减数据范围与时间复杂度
{
   int lack=1;
   x=x%10;
   while(n!=1)
   {
       if(n&1)lack=lack*x%10;  //ans存储奇数次幂时,由于幂整除,少乘的那些原底数
       x=(x%10)*(x%10);
       n>>=1; //b>>=1用于对b整除2,相较于b/=2,效率更高
   }
   return lack*x%10;
}    
int main()
{
   int t;
   cin>>t;
   ll n;
   while(t--)
   {
       cin>>n;
       cout<<Pow(n,n)<<endl;
   }
   return 0;
}

2、求AB的最后三位整数

参考思路

  类似于第一题,看到只求最后三位数,其实就是暗示所有结果都mod1000,那么就可能用到我们的乘法的取模运算法则;看到A,B的范围104,很可能暴力幂的方式还是不太行,继续用快速幂
​  思路就又有了:还是快速幂+取模乘法性质一条龙解题。

参考AC代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll quick_pow(ll x,ll n){
	ll lack=1,x=x%1000;
	while(n!=1){
		if(n&1)lack=((lack%1000)*(x%1000))%1000;	
		x=((x%1000)*(x%1000))%1000,n/=2;
	}
	return ((lack%1000)*(x%1000))%1000;
}
int main(){
	ll a,b;
	while(cin>>a>>b&&(a+b)){
		cout<<quick_pow(a,b)<<endl;
	}
	return 0;
}

参考

《2021杭电ACM-LCY算法培训入门班》
  有任何问题,可以在评论区留言~
    祝各位码力飞进!!!谢谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值