快速幂详解(通俗易懂!)
本文为博主原创文章,未经允许不得转载。如有问题,欢迎指正!
快速幂详解
- 问题背景:
- 粗暴求解:
- 如何解决超时问题:
- 如何解决乘法溢出的问题:
- AC完整代码
- 总结:
- (一)一些说明
- (二)特殊情况&代码模板:
- (三)类似思想的题:
- 上一篇博客:[leetcode 328.奇偶链表](https://blog.csdn.net/IAMLSL/article/details/107898620)
问题背景:
题目描述:
求 a 的 b 次方对 p 取模的值,其中 0≤a,b,p≤109 。
输入描述:
三个用空格隔开的整数a,b和p。
输出描述:
一个整数,表示a^b mod p
的值。
示例1
输入
2 3 9
输出
8
来源:牛客网
题目链接:https://ac.nowcoder.com/acm/contest/996/A
粗暴求解:
不考虑数据范围,我们可以很容易想到以下的解法:
typedef long long LL;
LL POW(LL a,LL b,LL p){LL res=1;
while(b){res=res*a;
b--;
}
return res%p;
}
看了题目的数据范围,发现这个算法会出现以下两个问题:
1. 程序超时。(b的值达到了109)
2. 乘法溢出。(res在累乘的时候可能太大)
如何解决超时问题:
1.超时原因:指数b过大、累乘的次数过多。故应该从指数b出发,降低累乘的次数。
2.我们知道任何一个正整数都可以由2的整数次幂相加得到。例如:
7=1+2+4 ,9=1+8 ,11=1+2+8,12=4+8,21=1+4+16。
3.令a的初始值为A,将a的值不断和自身相乘并不断赋给自身,那么A的指数也恰好为2的整数次幂。
具体的迭代过程如下:
a=a (初始值) (a==A1)
a=a*a (a==A2)
a=a*a (a==A4)
a=a*a (a==A8)
a=a*a (a==A16)
a=a*a (a==A32)
4.由(am) *(a n)=a(m+n) 可知,在以上的迭代过程中选择一些固定的a值和res进行累乘,就可以使得res等于Ab次方。
5.那么要选择哪几次迭代的a值进行累乘呢? 我们把b转化成二进制表示,就可以直观地看到b是由哪些2的整数次幂相加得到。例如b等于11,其二进制为(1011)2。故11=20 +21 +23 =1+2+8。A11 =(A1)*(A2)*(A8) =A(1+2+8) ,即选择A1 、A2 、A8 进行累乘可以得到A11。
6.判断b&1是否为1可以检测b的二进制的最后一位是否是1。不断将b右移就可以检测b的每一位。
7.检测b在二进制表示下的每一位 ,若当前位是1,就把当前的迭代值a累乘。
8.循环由b- -改为b=b>>1,时间复杂度变为为log2b。
计算a^45的过程模拟:
具体代码如下(记为代码一):
typedef long long LL;
LL POW(LL a,LL b,LL p){int res=1;
while(b){
if(b&1)res=res*a; / /若b&1==1,就选择当前的迭代值a和res累乘。
a=a*a; / /迭代构造a,a是初始值的2的整数次幂
b=b>>1; / /将b右移一位
} / /以上计算得到a^b
return res%p; / /取模
}
此做法降低了时间复杂度,但并没有解决溢出的问题。
如何解决乘法溢出的问题:
首先引入一个模运算的规律:(a * b) % p = (a % p * b % p) % p 。还可以推广到任意多个因数因数相乘再取模:(a*b*c)%d=(a%d*b%d*c%d)%d。即任意多个因数相乘再取模等于各因数取模后的乘积再取模。对于上面优化过后的代码,只需每一步都对因数(a*a)取模,同时对“各因数取模后的乘积”res*a再取模,即可维持模值的正确性。由于每一步都取模,累乘后的值就不会溢出了。(注意符号*和符号%的优先级一样,故从左到右计算表达式)。
具体代码如下(记为代码二):
typedef long long LL;
LL POW(LL a,LL b,LL p){int res=1;
while(b){
if(b&1)res=res*a%p; / /对“各因数取模后的乘积”res*a再取模
a=a*a%p; / /对因数(a*a)取模
b=b>>1;
}
return res;
}
AC完整代码
#include<iostream>
using namespace std;
typedef long long LL;
LL POW(LL a,LL b,LL p){LL res=1%p; / /应对b=0而p=1的情况特殊情况while(b>0){if(b&1) res=res*a%p;a=a*a%p;b=b>>1; } return res;
}
int main(){LL a,b,p; cin>>a>>b>>p; cout<<POW(a,b,p);
return 0;
}
总结:
(一)一些说明
严格地说,快速幂算法应该指的是上面解决了时间复杂度的代码一,且代码返回值是res。快速幂只是快速求a^b。(字面上应该要这样理解才合理吧)。即快速幂算法代码如下。
typedef long long LL;
LL POW(LL a,LL b,LL p){int res=1;
while(b){
if(b&1)res=res*a; / /若b&1==1,就选择当前的迭代值a和res累乘。
a=a*a; / /迭代构造a,a是初始值的2的整数次幂
b=b>>1; / /将b右移一位
} / /以上计算得到a^b
return res; / /取模
}
至于代码二,应该叫“快速幂取模算法”更合理吧。我觉得只有把两者区分一下,逐个弄懂,才不至于搞懵了。(很多资料都是直接上最终代码二,使得“求幂”和“取模”冗杂在一起,没有递进的逻辑分析,不容易理解)。
(二)特殊情况&代码模板:
在一些oj上,题目的测试数据可能会是b=0,而p=1的情况(此时res应该为0)。如果按上面的代码二,while循环没有执行则会返回错误值1。
typedef long long LL;
LL POW(LL a,LL b,LL p){int res=1%p; / /应对b=0而p=1的情况
while(b){
if(b&1)res=res*a%p; / /对“各因数取模后的乘积”res*a再取模
a=a*a%p; / /对因数(a*a)取模
b=b>>1;
}
return res;
}
(三)类似思想的题:
附上一篇思想几乎一致的题目:牛客网 64位整数乘法。以便对比学习。
上一篇博客:leetcode 328.奇偶链表
快速幂详解(通俗易懂!)相关推荐
- 第M题 快速幂详解!: 给出3个正整数A B C,求A^B Mod C。
给出3个正整数A B C,求A^B Mod C. 例如,3 5 8,3^5 Mod 8 = 3. Input 3个正整数A B C,中间用空格分隔.(1 <= A,B,C <= 10^9) ...
- 数学--矩阵快速幂详解
引导: 我们之前都学快速幂: 矩阵也是可以相乘,方阵可以自乘,即乘幂运算. 作用: 将线性递推,优化log2nlog_{2}nlog2n 模板: 定义矩阵的阶 const int len = 15; ...
- (详解)矩阵快速幂详解与常见转移矩阵的构造
目录 转移矩阵求解套路 常见转移矩阵1-斐波那契矩阵 承接套路 常见转移矩阵2-类斐波那契数列 常见转移矩阵3-幂常数 前缀和 具体DP问题 ----------------------------- ...
- 矩阵快速幂详解--用矩阵幂解决的多种问题
最经典的题目 以及洛谷一大堆相似题斐波那契升级版,广义斐波那契等等,都是相关的题目.一般而言我们求解斐波那契无非是不断地向前迭代,但是这样的效率实在是太低了.对于nnn的规模如此之大的题目应该如何求解 ...
- 快速幂和矩阵快速幂详解+模板
1.快速幂 一般的,我们都知道求只需要连续乘3次2就能得到,那么等于多少呢?其实这个一很简单,不就是13个2相乘吗,连续乘13次2就行了.那么,呢? 是不是要连续乘100次.1000次,我们将这类问题 ...
- 快速幂详解(超详细!!!)
目录 快速幂的作用 快速幂的实现 思路 Code 例题 快速幂的作用 当我们做一些高次幂的计算时,就不能直接进行暴力的计算.例如:需要计算2n2^n2n并且n≤1018n\le 10^{18}n≤10 ...
- python符号格式化设置区间_Python 数值区间处理_对interval 库的快速入门详解
使用 Python 进行数据处理的时候,常常会遇到判断一个数是否在一个区间内的操作.我们可以使用 if else 进行判断,但是,既然使用了 Python,那我们当然是想找一下有没有现成的轮子可以用. ...
- 数据结构 串 KMP 模式匹配详解 通俗易懂
KMP 模式匹配详解通俗易懂 KMP 模式匹配是解决字符串匹配的问题 一.原始的字符串暴力匹配 要点:子串的第一个字符匹配成功主串的字符后就依次匹配子串后面的字符,直到子串匹配结束 代码: publi ...
- Reactor And Gev 详解 通俗易懂
reactor 详解 在类似网关这种海量连接, 很高的并发的场景, 比如有 10W+ 连接, go 开始变得吃力. 因为频繁的 goroutine 调度和 gc 导致程序性能很差. 这个时候我们可以考 ...
最新文章
- 【图论专题】最小生成树的扩展应用
- 理解可变参数va_list、va_start、va_arg、va_end原理及使用方法
- 通过 SpringBoot 中使用 lombok 实现自动创建JavaBean的get/set方法、全参/无参构造方法、toString()、equals()、hashCode()
- linux源代码调用,linux – 哪里可以找到系统调用源代码?
- 23种设计模式之解释器模式
- MyBatis框架笔记04:MyBatis关联数据查询
- 鸿星尔克与钉钉签署专属音视频合作 全国5千家门店均可接入
- 小程序升级服务器内存需要注意什么,小程序服务器内存需要多大
- 深度比较Java循环的性能
- Android Studio 使用Method trace,查看某进程的所有线程trace的方法
- java定义一个二维数组
- python微信公众号翻译功能_Python实现微信翻译机器人的方法
- OneNote同步问题,提示没有权限
- 打赢下一场游戏大战!PlayStation能满足玩家一切需求?
- 任务栏没有计算机快捷方式,电脑程序在运行但是任务栏没有图标怎么处理 电脑程序在运行但是任务栏没有图标如何处理...
- oracle 的dba users表,oracle DBA 常用表和视图
- 照片恢复软件哪个好用?5个好用的照片恢复软件推荐
- 如何搭建一个自己的网站
- ubuntu 16.04下安装pytorch配置caffe2(cuda 9.0+cudnn 7.6)
- HTB_Weak RSA