都知道算某个数的幂需要线性的复杂度,为了优化复杂度,就出现了所谓的快速幂。

快速幂的代码很短,但是要原理需要一点心思。

首先,我们知道,

a^b = a^c * a^d (c+d=b)

那么,不就可以通过 a^b = a^b1 * a^b2 * a^b3... * a^bn (b1 + b2...+bn = b) 来快速获得a^b吗?这个方法的优越性在于,如果可以线性的求出a^b1~a^bn,不就是很快的算法吗?

因为a^b=a^c*a^d,c+d=b这条原理,我们的目标是找到普遍满足 b = b1 + b2 ... + bn的规律。所以,我们必须要找出一个统一的方法来确定b对应的b1~bn分别是多少。

如果各位知道进制转换的原理,就可以知道:一个n进制的数转为十进制等于 求和( i = 0~n-1 ) n ^ i * 第 i 位的数字。

例如,十进制数10的二进制数1010可以表示为: 2^0 * 0 + 2^1 * 1 + 2^2 * 0 + 2^3 * 1 (这里挺绕的,为了方便理解和验证,2^3*1意思是 2进制,第3位,二进制数字1010中第三位是1)

仔细一想,这不就是我们要的"确定b对应的b1~bn分别是多少"吗?这也是快速幂的原理所在,将一个质数分解为许多可以线性一个个求出的2的次方的幂。

我再次重申a^b=a^c*a^d,c+d=b这条原理,不能不搞清楚乘法和加法的关系,我们之前得到的加法规律实际上是应用于c+d=b这里的,最后的计算还是要用乘法。

之前我一直在说,这个方法或者说规律的优越性在于我们可以线性的求出相加的每一个指数。

例如,我知道十进制数10,也就是二进制数1010,我就可以在线性时间复杂度里得到 2^0 * 0 、 2^1 * 1 、 2^2 * 0 、 2^3 * 1。

说了这么多废话,目的在于接下来的这一条原理。

( a^(2^0) )^2 =   a^(2^1) 你可以亲自验证一下这条神奇的性质。不仅是对0+1 = 1有效,你可以把0换成任何一个数,把1换成那个数+1,看看还会不会生效。

对于每个数k,归纳一下,就是 (a^(2^k) )^2 = a^(2^(k+1) ) 。其实自己稍微一想,就明白是怎么回事了。

在初中数学中,我们知道 (a^b)^k = a^(b*k)。   所以,(a^(2^k) )^2 = a^( (2^k)*2 )。而2^k = 相乘(i = 1~k) 2。(2*2*2*2...*2,k个2),所以(2^k)*2也就是“k个2相乘再乘2”不就是“k+1个2”,2^(k+1)么。
总结一下完整的推导过程, (a^(2^k) )^2 = a^(2^k)*2 = a^(2^(k+1) )。
实际上这个推导非常简单,写出来是为了清晰思路,根据题意更好利用快速幂利用的性质。
 
 
最后剩下一个点没有说,那就是在二进制中,每一位只有1和0两种状态,在0的时候看看我们之前的分析,实际上是没有作用的(理解为指数相加时+0,或者相乘时乘1),为了不让它“滥竽充数”地也来分一杯羹,我们使用&运算符,判断这个二进制最后一位是否为0,如果是0我们就不能在最后乘上它。当然,在看到代码之前看这句话自然是一头雾水,我们接下来看代码:
 
typedef long long ll;
ll poww(ll a, ll b)//a^b
{
ll re = ;
while(b != )
{
if(b & )
re *= a;
a = a * a;
b >>= ;
}
return re;
}

解释一下代码。

b & 1代表着我们之前的判断"为了不让它“滥竽充数”地也来分一杯羹,我们使用&运算符,判断这个二进制最后一位是否为0"。为什么要判断“最后一位”,因为我们在判断完指数的二进制的某一位后,那一位不再有用,

所以我们使用 b >>= 1也就是位运算符「右移」来消除使用过的那一位。由于我们不想计算当前是哪一位,并且希望代码尽可能的简便高效,我们只能不计当前是多少位,

用之前说过的利用平方来求下一个b1~bn中的一个。(b=Σ(i=1~n) bi,Σ为求和,求b1+.2+...bn,我有点啰嗦,但能让更多人看懂)。

所以,为了简洁高效地完成任务,实际上我们把原来的 一个n进制的数转为十进制等于 "求和( i = 0~n-1 ) n ^ i * 第 i 位的数字”  变成了 “ 求和( i = 0~n-1 ) n ^ i ”。

例如,十进制数10的二进制数1010按照我们原来的方式是: 2^0 * 0 + 2^1 * 1 + 2^2 * 0 + 2^3 * 1,而在代码里是 2^0  + 2^1  + 2^2  + 2^3

这就不可避免的造成了在某个数的二进制的某一位是0而造成本该是0的指数被我们计算成了别的数。所以,我们一定要在一开始加上二进制下最后一位是否为0的条件,如果不是0那么就可以把当前得到的结果乘到我们的最终结果变量上,如果是0则不能乘到最终结果变量上,但是a依然要平方,不能因为这一位数字的结果被忽略而不计下一位数字的结果应当按照我们之前的方法线性求出本当乘上的数字。(例如,按照我们之前1010的例子,2^0 * 0被我们忽略,但是如果上一位数字的结果被忽略就不考虑下一位的话,这一位的指数就是2^0*1了,与我们期待的结果不符。)

说了这么多,发现自己想说的其实可以精炼一下,把自己的思考过程部分隐去。但是转念一想,对于第一次听说线性筛的OIer或者别的学习者需要详细的描述,而我自己也不能保证一直记住快速幂的原理,权当整理了。

如果本篇博客有差错或者不恰当之处,请多多指正。

【OI】关于快速幂的简单理解的更多相关文章

  1. hdu 1575 Tr A(矩阵快速幂,简单)

    题目 和 LightOj 1096 - nth Term  类似的线构造一个符合题意的矩阵乘法模版,然后套快速幂的模版,具体的构造矩阵我就不作图了,看着代码也能理解吧 #include<stdi ...

  2. hdu 1757 A Simple Math Problem (矩阵快速幂,简单)

    题目 也是和LightOJ 1096 和LightOJ 1065 差不多的简单题目. #include<stdio.h> #include<string.h> #include ...

  3. zoj 2974 Just Pour the Water (矩阵快速幂,简单)

    题目 对于案例的解释请见下图: 这道要变动提取一下矩阵,之后就简单了 具体解释可看代码: #include <string.h> #include <stdio.h> #inc ...

  4. LightOj 1065 - Number Sequence (矩阵快速幂,简单)

    题目 和 LightOj 1096 - nth Term 差不多的题目和解法,这道相对更简单些,万幸,这道比赛时没把模版给抽风坏. #include<stdio.h> #include&l ...

  5. LightOj 1096 - nth Term (矩阵快速幂,简单)

    题目 这道题是很简单的矩阵快速幂,可惜,在队内比赛时我不知什么时候抽风把模版中二分时判断的 ==1改成了==0 ,明明觉得自己想得没错,却一直过不了案例,唉,苦逼的比赛状态真让人抓狂!!! #incl ...

  6. hdu 1757 (矩阵快速幂) 一个简单的问题 一个简单的开始

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1757 题意不难理解,当x小于10的时候,数列f(x)=x,当x大于等于10的时候f(x) = a0 * ...

  7. poj 3070 Fibonacci(矩阵快速幂,简单)

    题目 还是一道基础的矩阵快速幂. 具体的居者的幂公式我就不明示了. #include<stdio.h> #include<string.h> #include<algor ...

  8. 矩阵快速幂/矩阵加速线性数列 By cellur925

    讲快速幂的时候就提到矩阵快速幂了啊,知道是个好东西,但是因为当时太蒟(现在依然)没听懂.现在把它补上. 一.矩阵快速幂 首先我们来说说矩阵.在计算机中,矩阵通常都是用二维数组来存的.矩阵加减法比较简单 ...

  9. (快速幂)51NOD 1046 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^ ...

随机推荐

  1. Bitmap.createBitmap函数有6个重载方法

    位图剪切参考重载方法4和6,重载方法6比较简单 public static Bitmap createBitmap (Bitmap src)从原位图src复制出一个新的位图,和原始位图相同 publi ...

  2. Spring boot 控制台打印sql

    在application.ym中加入: logging: level: com.wechat.cwbt.dao : debug 发现无效 在log4j.properties中加入: log4j.log ...

  3. http和socket

    大多数情况下都是使用Http协议做网络通信的,少数情况下,如扣扣之类的即时通讯,就是用Socket建立长链接 Http一般都是短连接的,即客户端和服务端通讯一次后,服务端就关闭连接 Socket是长连 ...

  4. Javascript创建对象几种方法解析

    Javascript创建对象几种方法解析 Javascript面向对象编程一直是面试中的重点,将自己的理解整理如下,主要参考<Javascript高级程序设计 第三版>,欢迎批评指正. 通 ...

  5. C++命名空间、标准库(std,全局命名空间)

    背景 别人遇到的问题: C++ 全局变量不明确与 using namespace std 冲突 我遇到的问题与他相似,函数调用冲突 using namespace std; class compare ...

  6. Zabbix微信告警

    Zabbix微信告警 摘要 Zabbix可以通过多种方式把告警信息发送到指定人,常用的有邮件,短信报警方式,但是越来越多的企业开始使用zabbix结合微信作为主要的告警方式,这样可以及时有效的把告警信 ...

  7. php - namespace篇

    之前没有系统学习过PHP语言,直接上手TP框架了,所以认为namespace和use是TP框架的一部分,最近学习语言模块的时候遇到了这个问题,所以汇总了一下. PHP中命名空间可以解决两类问题: 用户 ...

  8. 【转载】分布式系列文章——Paxos算法原理与推导

    转载:http://linbingdong.com/2017/04/17/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0 ...

  9. vim 编辑器使用法则

    vim 编辑器使用法则 Vi编辑器有3种使用模式:一般模式.编辑模式和命令模式. $SHELL:查看当前默认shell类型  $BASH_VERSION:查看当前shell版本 3.一般模式: 光标移 ...

  10. Microsoft Excel 准确按照一页的宽度和高度打印

    设置 Microsoft Excel 准确按照一页的宽度和高度打印 Sheet1. VBA复制  With Worksheets("Sheet1").PageSetup  .Zoo ...