快速幂算法
原理
通常来说我们计算指数幂的时候最自然的想法是通过累乘计算的,比如计算 m n m^n mn,只需将 m m m累乘 n n n次即可,但是这样的复杂度为 O ( n ) O(n) O(n)很有可能无法满足需求,所以我们考虑对其优化:
首先,考虑公式
a
n
=
a
i
+
j
=
a
i
∗
a
j
,
(
i
+
j
=
n
)
a^n=a^{i+j}=a^i*a^j, (i+j=n)
an=ai+j=ai∗aj,(i+j=n)
不妨设
i
≥
j
i\geq j
i≥j,则我们只需计算出
a
i
,
a
j
a^i,a^j
ai,aj(需
i
i
i次,因为在计算到
a
i
a^i
ai之前可以计算得到
a
j
a^j
aj,只需将
a
j
a^j
aj存起来即可)再将
a
i
a^i
ai和
a
j
a^j
aj乘起来(1次)总共需要计算
i
+
1
i+1
i+1次,并且通过迭代还可以继续减少计算次数,从而优化算法。个人觉得类似秦九韶算法的思想
比如当我们计算类似 5 7 5^7 57,可以计算 5 4 + 3 5^{4+3} 54+3,进一步拆分可以得到 5 4 + 2 + 1 5^{4+2+1} 54+2+1,这样我们按照上面的算法只需计算 4 + 1 = 5 4+1=5 4+1=5次就可以得到 5 7 5^7 57的结果。但事实上需要的计算次数更少。在讨论为什么更少之前我们先来看一下拆分后最后的结果要满足什么条件。
假设最终结果是
a
m
k
+
m
k
−
1
+
.
.
.
+
m
1
+
m
0
a^{m_{k}+m_{k-1}+...+m_1+m_0}
amk+mk−1+...+m1+m0
- 首先, n = ∑ i = 0 k m i n=\sum_{i=0}^{k}mi n=∑i=0kmi
- 其次,在计算得到 a m k a^{m_k} amk后必然已经计算得到了 a m k − 1 , a m k − 2 , . . . , a m 1 a^{m_{k-1}},a^{m_{k-2}},...,a^{m_1} amk−1,amk−2,...,am1
- 能通过尽量少的 a m i a^{m_i} ami项得到 a m k a^{m_k} amk, ( i = 1 , 2... k − 1 ) (i=1,2...k-1) (i=1,2...k−1)
关键在第三条, a m i a^{m_i} ami和 a m k a^{m_k} amk之间的关系越简单,涉及项数越少越好。
很明显,这样的 m k , m k − 1 , . . . , m 0 m_{k},m_{k-1},...,m_{0} mk,mk−1,...,m0必然是由同一个规则生成的一族数列,当然并不是任何实例都用到由这个规则生成的所有数。
这样的数列,可以考虑{ 1 , 2 , 4 , 8 , . . . , 2 i , . . . 1,2,4,8,...,2^i,... 1,2,4,8,...,2i,...}
二进制明显满足上述要求:任意整数都有其二进制表示;计算
a
2
k
a^{2^k}
a2k时必然得到了
a
2
i
,
(
0
≤
i
<
k
)
a^{2^i},(0\leq i<k)
a2i,(0≤i<k)
a
m
k
a^{m_k}
amk;计算
a
2
k
a^{2^k}
a2k:
a
2
k
=
a
2
⋅
2
k
−
1
=
a
2
k
−
1
+
2
k
−
1
=
a
2
k
−
1
⋅
a
2
k
−
1
a^{2^k}=a^{2\cdot 2^{k-1}}=a^{2^{k-1}+2^{k-1}}=a^{2^{k-1}}\cdot a^{2^{k-1}}
a2k=a2⋅2k−1=a2k−1+2k−1=a2k−1⋅a2k−1
到了这一步,我们可以给出代码实现了
算法实现
并非所有的 a 2 i a^{2^i} a2i都是 a m k a^{m_k} amk,但是要求出 a m k a^{m_k} amk我们必须要知道恰好小于 a m k a^{m_k} amk的 a 2 i a^{2^i} a2i,因此,我们需要将计算过程中的 a 2 i a^{2^i} a2i都保存起来,同时这个 a 2 i a^{2^i} a2i只需要保存一个循环,因为我们需要的是恰好小于 a m k a^{m_k} amk的 a 2 i a^{2^i} a2i,每次循环进行一次更新即可。
我们还需要将指数转化为二进制,这一步和计算 a m k a^{m_k} amk同时进行,回想一下转化二进制吧步骤。每次判断一次当前指数的最后一位是否为1(二进制)若为一,则说明当前的 a 2 i a^{2^i} a2i是我们所需要的 a m k a^{m_k} amk,否则不是,同时每次判断完事后将指数左移一位。(感觉很像原码一位乘)
下面是代码
#define ll long long
ll solve(ll base, int index)
{
ll ans = 1;
while (index) {
if (index&1) ans *= base;
base *= base;
index>>=1;
}
return ans;
}