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

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

首先,我们知道,

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. JAVA基础——设计模式之观察者模式

    观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式.模型-视图(Model/View)模式.源-监听器(Source/Listener)模式或从属者(Dependen ...

  2. JS、CSS、Image预加载

    Image预加载 <div class="hidden"> <script type="text/javascript"> var im ...

  3. MFC定时器的使用

    巧妙地使用定时器能达到意想不到的效果,写界面的时候能实现渐变,也能帮助多线程控制等我们知道,在VC的MFC中,已经为我们封装好了很多全面和强大的函数集,所以在MFC编程时,巧妙地调用MFC函数库可以为 ...

  4. CSU1022: 菜鸟和大牛

    1022: 菜鸟和大牛 Submit Page   Summary   Time Limit: 1 Sec     Memory Limit: 128 Mb     Submitted: 1390   ...

  5. 笔试算法题(12):整数的string到int转换 & 两个栈实现队列

    出题:将输入的表示整数的字符串转变为对应的整数值: 分析: 每当右边增加一位,说明之前的sum应该高一个数量级,所以*10.由于这两个实现仅仅考虑正规的.正整数输入,所以需要一个Wrapper函数,其 ...

  6. python 1-1模块介绍和使用

    1. 什么是模块 1.1 模块就是一系列功能的集合体 1.1.1 模块有三种来源 1.内置的模块 2.第三方的模块 3.自定义模块 1.1.2 模块的格式: 1.使用Python编写的.py文件 2. ...

  7. yum 软件管理器

    yum软件管理器 yum是一个强大的软件包管理器,能够自动解决安装时rpm包之间的依赖关系. 一.使用yum管理软件包 1.使用命令 yum help 查看使用方法 [root@majinhai ~] ...

  8. 【转载】Raft 为什么是更易理解的分布式一致性算法

    一致性问题可以算是分布式领域的一个圣殿级问题了,关于它的研究可以回溯到几十年前. 拜占庭将军问题 Leslie Lamport 在三十多年前发表的论文<拜占庭将军问题>(参考[1]). 拜 ...

  9. UvaLive 4872 Underground Cables (最小生成树)

    题意: 就是裸的最小生成树(MST), 完全图, 边长是实数. 分析: 算是复习一下MST把 方法一: prim 复杂度(n^2) #include <bits/stdc++.h> usi ...

  10. 修改windows 2012/win8、win7远程桌面连接默认端口的方法

    远程桌面连接服务基于端口3389,入侵者一般先扫描主机开放端口,一旦发现其开放了3389端口,就会进行下一步的入侵,所以我们只需要修改该务默认端口就可以避开大多数入侵者的耳目. 修改windows 2 ...