# 快速幂
快速幂,又叫欧拉降幂。它的作用也很简单,就是快速的求出akmodp 的值。
# 问题
给出 n 组数 a,k,p ,每组数据都求出 akmodp 的值是多少。
# 数据范围
1≤n≤100000
1≤a,k,p≤2∗109
# 分析
从问题出发。因为 a
和 k
的范围都很大,最大可以到 2∗109 。所以,比较容易想出来的做法是为了避免高精度计算,可以根据同余定理,边乘边取模。( 同余
非本文内容,此处就不深入展开啦~)
| res = 1 |
| for i in range(k): |
| res = res * a % p |
但是,这种做法的时间复杂度是O(k) ,而 k
的取值最大也会到 2∗109 。妥妥的超时。
# 快速幂(欧拉降幂)
而使用快速幂可以把时间复杂度优化到 O(logk) ,即便 k
取到了最大值,也只需要循环 30
次左右,极大的提高了计算速度。
# 核心思路
# 一。拆分akmodp
拆分依赖一个重要的知识点 乘除法指数运算方法
:在乘除法的指数运算中,同底数幂相乘,底数不变,指数相加;同底数幂相除,底数不变,指数相减。
用公式表示为:
an∗am=an+m
an÷am=an−m
如何拆分呢?
我们可以把 k
拆分成二进制的表示形式。使用二进制来表示一个比较大的数,这种技巧我们在其他地方也多有应用,比如多重背包的二进制优化,状态压缩 DP。
举例:当前 k=11
,我们可以把 k
拆分成11=(1011)2 。
现在,想一下把二进制还原成十进制是怎么做的?一般使用早就学过的 “按位记数法”。即,从低位到高位,列出 2 的指数幂,指数从 0 开始,每右移一位指数就加一,当前位若是 1 就累加起来,最后的结果就是十进制。
举例:当前二进制是 11=(1011)2 ,那么按照 “按位记数法” 的展开公式就是 1∗20+1∗21+0∗22+1∗23,最后计算结果为 11
。
假设k=20+21+22+...+2x,那么根据乘除法指数运算方法,ak 可以写成 a(20+21+22+...+2x) 。
继续推导,上述公式又可以写成 a20∗a21∗a22∗...∗a2x 的形式。
这一步最大可以分解出的指数是log(k) ,也就是 x
的取值范围最大是log(k) 。
现在,经过一番推导后,我们发现只要计算最终的那个公式就可以计算出 ak 。
# 二。预处理数据
我们可以把最终公式中的数据(a20,a21,a22,...,a2x)都预处理出来,这样计算的时候就可以直接用。
其中第一个数 a20 的值就是 a
本身;
而 a21 可以写成a21=a20∗2=(a20)2;
以此类推,a22 可以写成 a22=(a21)2。
最终找到以下规律:
a20=aa21=(a20)2a22=(a21)2⋮a2x=(a2x−1)2
也就是说预处理数据中,下面的每一项都是上一项的平方。这里 x
的取值与上面相同,也是log(k) 。
# 举例
当前a=4,k=5,p=10 ,此时有45mod10=4。
我们用快速幂的方式,计算上面结果。
拆分 k
。k=5=(101)2 ;
二进制计算十进制。(101)2=1∗20+0∗21+1∗22=5 ;
代入到45mod10=4(20+22)mod10 ,分解一下上面公式,得到4(20)∗4(22)mod10。
预处理数据。为避免计算中数据过大,根据同余定理,在计算过程中对每一步都进行取模运算:
420=4mod10=4a21=(a20)2=16mod10=6a22=(a21)2=36mod10=6
预处理结果代入到公式:
4(20)∗4(22)mod10=4∗6=24mod10=4
得到最终结果:45mod10=4 。
# Code
实现上,边计算最终结果边处理数据。每组数据的时间复杂度是O(logk),总共 n
组数据,总时间复杂度是O(nlogk) 。
| #include <iostream> |
| using namespace std; |
| |
| typedef long long LL; |
| |
| LL pow(int a, int k, int p) { |
| LL res = 1 % p; |
| while (k) { |
| |
| if (k & 1) res = res * a % p; |
| |
| a = a * (LL)a % p; |
| |
| k >>= 1; |
| } |
| return res; |
| } |
| |
| int main() { |
| int n; |
| scanf("%d", &n); |
| while (n -- ) { |
| int a, k, p; |
| scanf("%d%d%d", &a, &k, &p); |
| printf("%lld\n", pow(a, k, p)); |
| } |
| return 0; |
| } |
END