快速幂 学习笔记
说在前面
突然发现自己学了这么久的OI竟然还不会默写快速幂模板,这令我这个蒟蒻十分痛心。
于是花5min的时间复习了快速幂的原理并写下了这个笔记防止自己遗忘。
Pt.1 快速幂是什么
快速幂是一个很常用的trick,它的用途从它的名字就可以看出来,是用来快速求幂的。
这个东东在很多时候可以用到,甚至说与其他算法结合将一些看似不可能再优化的问题继续优化(比如矩阵快速幂)。
顺便说说,本文并不会对这些拓展的东西展开讲解,只是关注最底层的快速幂原理。
Pt.2 如何快速幂
快速幂,简单来说就是利用分治的思想将通过将指数拆分为二进制,并让底数反复平方,从而在 \(O(logn)\) 步内完成幂运算并达到优化的目的。
它基于一个叫整数的 b 进制表示定理的数学原理,表述为:
任意正整数 \(N\) 可唯一表示为:
[N = \sum_{i=0}^{k} a_i \cdot b^i]
其中 \(b \ge 2\),\(a_i \in \mathbb{Z}\),且 \(0 \le a_i < b\),\(a_k \neq 0\)。
看起来挺绕的不是吗?其实大概就是说任意一个整数,都能用“1、2、4、8、16……”这种翻倍的值(即指数的幂次),有且只有一种方式刚好凑出来。具体翻几倍则是以几进制(也就是 \(b\) )决定。
还有一个很重要的点。初一数学大家都学过吧?\(a^n \times a^m = a^{n+m}\).
所以我们就可以将指数拆分为二的几次方的和的形式,也就是 \(a^{2^k} \times a^{2^{k+1}} \times ......\) 这样,然后对于每个\(a^{2^k}\)用一个变量不断自乘翻倍来计算,最后再用一个变量把它们乘起来就可以了。
具体为什么是二进制,大概就是假设我们拆成 \(b\) 进制,计算某个数的 \(k\) 次方,循环次数是 \(\log_b k\) 次,但每一次迭代,底数要从 \(a\) 变成 \(a^b\)(需要 \(b-1\) 次乘法),并且还要处理当前位上的系数(最多乘 \(b-1\) 次),这就导致大进制的乘法运算次数甚至比二进制的次数还多。而且计算机本身底层逻辑便是二进制,二进制在硬件性能上就占有天然的优势。
Pt.3 实战应用
其实快速幂一般应用就是套个板子上去就完事了,一般不会有什么改动,也很少会说让你从另外一个东东建模到快速幂的。
目前暂时没找到什么好题,讲究着看吧。
P1226 【模板】快速幂
题目描述
给你三个整数 \(a,b,p\),求 \(a^b \bmod p\)。
输入格式
输入只有一行三个整数,分别代表 \(a,b,p\)。
输出格式
输出一行一个字符串 a^b mod p=s,其中 \(a,b,p\) 分别为题目给定的值, \(s\) 为运算结果。
输入输出样例 #1
输入 #1
2 10 9
输出 #1
2^10 mod 9=7
说明/提示
样例解释
\(2^{10} = 1024\),\(1024 \bmod 9 = 7\)。
数据规模与约定
对于 \(100\%\) 的数据,保证 \(0\le a,b < 2^{31}\),\(a+b>0\),\(2 \leq p \lt 2^{31}\)。
快速幂模板。没啥好说的实在要讲什么就看回上面Pt2吧,上代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int x,y,mod;
int fp(int a,int b){//求a^b
int res=1;//保存结果
while(b){//对指数进行分解
//记得取模
if(b&1)//等于"if(b%2==1)",但是位运算效率会更快
res*=a,res%=mod;
a*=a;//翻倍自增
a%=mod;
b/=2;
}
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>x>>y>>mod;
cout<<x<<'^'<<y<<' '<<"mod "<<mod<<'='<<fp(x,y);
return 0;
}

浙公网安备 33010602011771号