假设我们要计算 2102^{10}210 对1000取模的结果,可以很简单的得到24。但是如果要求 210002^{1000}21000 对1000取模的结果,常规方法就行不通了,因为常规的变量无法容纳这么大的数值。为此,需要借助数学技巧求解。

循环求余法

首先引入一个好理解的取模运算公式:
(a×b)%p=(a%p×b%p)%p(a \times b) \% p = (a \% p \times b \% p) \% p (a×b)%p=(a%p×b%p)%p
举个例子:
a=9,b=9(9×9)%6=(9%6×9%6)%6=(3×3)%6=3a=9, b=9\\ (9 \times 9) \% 6 = (9\%6 \times 9\%6)\%6 = (3 \times 3) \% 6 = 3 a=9,b=9(9×9)%6=(9%6×9%6)%6=(3×3)%6=3
受上面计算过程的启发,不难得到下面的取模运算公式:
xa%p=((x%p)a)%px^a \% p = \left( (x \% p)^a \right) \% p xa%p=((x%p)a)%p
即 a 个 x 的乘积可以分解为 a 个 x 对 p 余数的乘积再对 p 取余,即 ((x%p)a)%p\left( (x \% p)^a \right) \% p((x%p)a)%p 。

举个例子:
x=9,a=2,p=6(92)%6=(9%6)2%6=32%6=3x=9, a=2, p=6\\ (9^2) \% 6 =(9\%6 )^2\%6 = 3 ^2 \% 6 = 3 x=9,a=2,p=6(92)%6=(9%6)2%6=32%6=3
对于上面的例子,如果 a = 1000,难道最后还得计算 31000%63^{1000} \% 631000%6 吗?其实可以这么做:

在进行 a 次迭代计算 x 对 p 余数的过程中,先将第 iii 次迭代得到的余数用变量 rem 保存起来,在进行第 i+1i + 1i+1 次迭代的时候,由 (rem * (x%p)) % p 所得到的结果就是 xi+1%px^{i+1} \%pxi+1%p 的结果。

举个例子,求 x3%px^3 \% px3%p 的过程如下:

1,rem = x % p
2,rem = x^2 % p = ((x%p) * (x%p)) % p = (rem * (x%p)) % p
3,rem = x^3 % p = ((x^2%p) * (x%p)) % p = (rem * (x%p)) % p

可以看到这个过程的时间复杂度是 O(N),其中 N = a,代码实现如下:

# 求 (x^a) % p —— 循环求余法
def reminder1(x, a, p):rem = 1for _ in range(a):rem = (rem * (x%p)) % preturn remx, a, p = 2,100000000, 3
print(reminder1(x, a, p)) # 用时:9.06s

观察(rem * (x%p)) % p,发现既然括号的外面要用 p 取模,那么内部的(x%p)就显得没有必要了,因此可以简化为:(rem * x) % p

# 求 (x^a) % p —— 循环求余法
def remainder2(x, a, p):rem = 1for _ in range(a):rem = (rem * x) % preturn remx, a, p = 2,100000000, 3
print(reminder2(x, a, p)) # 用时:6.76s

快速幂求余

上面的过程中,我们每一次迭代都是计算 x%px\% px%p,所以需要迭代 a 次。其实这个过程还有优化的空间:

如果我们每次都计算 x2%px^2 \% px2%p,那只需要迭代 a/2 次。也就是说,可以把指数 a 级的问题转换为 a/2 级的问题,如果继续分解这个计算过程,又可以得到:
xa%p=(x2%p)a/2%p=((x2%p)2%p)a/4%p=...x^a \% p = (x^2 \% p)^{a/2} \% p = \left( (x^2 \% p)^2\%p \right)^{a/4} \% p = ... xa%p=(x2%p)a/2%p=((x2%p)2%p)a/4%p=...
最终计算 xa%px^a \%pxa%p 的时间复杂度仅为 O(logN)O(logN)O(logN) 。

实际上,上面的式子忽略了一个问题, a 可能是奇数也可能是偶数,因此对 a/2 的处理上需要遵循下面的公式:
xa%p={(x2%p)a//2%p,a为偶数[(x%p)(xa−1%p)]%p=[x(x2%p)a//2]%p,a为奇数x^{a} \% p=\left\{\begin{array}{l} \left(x^{2} \% p\right)^{a / / 2} \% p \qquad \qquad \qquad \qquad \qquad \qquad \qquad, a 为偶数\\ {\left[(x \% p)\left(x^{a-1} \% p\right)\right] \% p=\left[x\left(x^{2} \% p\right)^{a / / 2}\right] \% p} \quad\; ,a为奇数 \end{array}\right. xa%p={(x2%p)a//2%p,a为偶数[(x%p)(xa−1%p)]%p=[x(x2%p)a//2]%p,a为奇数​
式子中的 // 表示除法运算的结果向下取整。此计算过程的代码实现如下:

# 求 (x^a) % p —— 快速幂求余
def remainder3(x, a, p):rem = 1while a > 0:# 无论 a 是奇数或偶数,a//2的结果是一样的# 因此,对于奇数要进行特殊处理if a % 2: rem = (rem * x) % p# 底数变大为原来的平方:x = x ** 2 % p# 指数变为原来的一半a //= 2return rem
x, a, p = 2,100000000, 3
print(remainder3(x, a, p)) # 几乎等于0

为什么 rem 只在 a 为奇数的时候才计算呢?

假设 a = 4,那么

  • 第一次迭代后 a=2, x = x^2 % p;
  • 第二次迭代后 a=1,x = (x^2 % p)^2 %p;
  • 第三次迭代时,可以求得 rem = (1 * x) % p,且 a = 0;
  • 结束循环,返回了 rem。

假设 a = 5,那么:

  • 第一次迭代后 rem = x%p,a=2, x = x^2 % p;
  • 第二次迭代后 a=1,x = (x^2 % p)^2 %p;
  • 第三次迭代时,可以求得 rem = (rem * x) % p,且 a = 0;
  • 结束循环,返回了 rem。

也就是说无论 a 是奇数还是偶数, a//2的计算结果都是交替出现的奇数和偶数,并且最终都会得到 a = 1 计算出 rem,然后结束循环,返回 rem。因此只需要在 a 为奇数的时候计算 rem 即可保证最后得到的 rem 就是我们想要的值。

参考文章:

面试题14- II. 剪绳子 II(数学推导 / 贪心思想 + 快速幂求余,清晰图解)

快速幂算法(全网最详细地带你从零开始一步一步优化)

【面试相关】python实现快速幂取余算法详解相关推荐

  1. Python实现快速幂取余算法

    引子 今天无意中,看到了慧科教育科技集团有限公司-后厂理工学院 的AI相关学员招募信息,要求颇高,还要做编程自测题,并且达到60分才建议进行课程的学习.我忍不住发送了报名信息,得到了自测题.看到了如下 ...

  2. 快速幂取余算法总结详解

    废话不多说, 直接步入正题. 现在oj网站的题或者竞赛的题,如果a的b次幂且b很大,那么题中大多会让你把结果对一个数取余也就是求模,例如a^b%c这种,当然如果是考高精度的题除外. 接下来我将提供一种 ...

  3. 快速幂||取余运算【模板】(洛谷P1226题题解,Java语言描述)

    题目要求 P1226题目链接 分析 标准的快速幂取模算法板子,之前这个算法我在这篇文章中讲过了:<快速幂算法详解&&快速幂取模算法详解>. 这里选择使用比较简单的API实现 ...

  4. 【数学】幂运算与快速幂取余

    一.幂运算 1.幂的定义: 的幂就是多个相乘,记作,表示的次幂,即个相乘,我们把叫做底数,叫做指数. 2.幂的运算: 我们可以用公式将幂运算进行简化,公式如下: 3.求幂方法: (1). Python ...

  5. C语言快速幂取模算法小结

    资料链接:http://www.jb51.net/article/54947.htm C语言实现的快速幂取模算法,是比较常见的算法.分享给大家供大家参考之用.具体如下: 首先,所谓的快速幂,实际上是快 ...

  6. 【算法分析与设计】快速幂算法与快速幂取模算法

    文章目录 快速幂算法 算法分析 算法实现 位运算优化 BigInteger支持 快速幂取模算法 算法优点 算法推导 算法实现 BigInteger支持 本文完整代码实现(Java语言描述) 快速幂算法 ...

  7. python中如何取余_基于python 取余问题(%)详解

    取余的公式: 余数=除数-被除数*商 python的的余数是按照整除(向下取整)得到的商来计算的. 取余问题主要分为 : 正数与正数,负数与负数,正数与负数 ,0 正数与正数 #大数/小数:因为得出的 ...

  8. python取余还是相乘_python取余还是相乘_基于python 取余问题(%)详解

    取余的公式: 余数=除数-被除数*商 python的的余数是按照整除(向下取整)得到的商来计算的. 取余问题主要分为 : 正数与正数,负数与负数,正数与负数 ,0 正数与正数 #大数/小数:因为得出的 ...

  9. 大数取余运算(详解)

    大数取余运算(详解) //(19^3 mod 23)^2 mod 31=25 //a=19 b=3 c=23 d=2 e=31 #include<stdio.h> long long po ...

最新文章

  1. Javascript与正则表达式个人总结与收录--高级篇
  2. springboot项目 访问不到静态资源css
  3. HTML5 details 标签
  4. instancetype和id的区别
  5. adb logcat 保存_adb命令——日志命令详解——全部输出到桌面: adb logcat c:\users\del\desktop\log.txt...
  6. Event Tracing for Windows
  7. 多目标遗传算法与优化的关系
  8. OSAL操作系统分析(添加自定义任务)
  9. 金庸年度最新十大广告
  10. in comment after two dashes (--) next character must be > not (position: START_TAG seen ...
  11. 事态升级管理程序范本_项目管理事态升级流程
  12. EXCEL 子母饼图
  13. 电脑计算机硬盘怎么加,电脑怎么增加硬盘内存
  14. 教你如何赚你的第一桶金 - 2048(包括源代码)
  15. 石墨笔记,为知笔记和Effie哪个更适合商业机构提案人员?
  16. 关于康托展开的用途及写法
  17. 鱼眼图像畸变校正——透视变换
  18. tar包解压和打包方法
  19. c语言实验指导修改版,高级语言程序设计(C语言)实验指导书(修改版).doc
  20. Rootkit 之 adore-ng 模拟木马病毒

热门文章

  1. linux下面navicat调整字体
  2. 语音识别(三)——声学模型, 解码器技术
  3. 从版本库看开源项目的发展史
  4. mybatis中foreach标签详解
  5. Android基础 EventBus3 0实用教程
  6. Node环境的配置与安装(windows系统)(超详细)
  7. 基于微服务API级权限的技术架构
  8. Android菜鸟的成长笔记(5)——Android系统源代码你下载了吗?
  9. 扩展easyui tree的两个方法 获取实心节点
  10. SQL Server之体系结构