快速幂算法
引理
- (a * b) % c = ((a % c) * (b % c)) % c.
- &与运算,一般用于取位,如 a&1表示取a二进制下的个位
- >>位运算,表示右移,如>>1表示除以2,比通常除法效率更高
- \(a^b = a^{b_0}*a^{b_1*2}*...*a^{b_n*2^n}\)
用途
主要用于大数取模
朴素算法存在效率太低和计算过程中超出数据范围的情况,必须进行优化
代码
int quick_pow(int a, int k, int mod)
{
int ans = 1;
a = a % mod;
while(k != 0)
{
if(k & 1) ans = (ans * a) % mod;
k >>= 1;
a=(a * a) % mod;
}
return ans;
}
int qow(int x, int y, int z)
{return y ? (y & 1 ? x * qow(x, y-1,z) % z : qow(x * x % z, y / 2,z)) : 1;}
拓展——矩阵快速幂
对于矩阵的定义以及矩阵乘法,就不多赘述了,直接切入正题
思路
矩阵快速幂和普通快速幂过程是一样的,只要将对应的运算规则变成矩阵运算规则就好。这里我选择重载运算符,当然也可以写个函数来实现。
而矩阵一般会选择封装起来,我对C++不是很熟悉,更倾向于用结构体来封装。
代码
//k,mod,maxn为全局变量
struct Mat
{
ll f[maxn][maxn];
void cls(){memset(f, 0, sizeof(f));}//全部置为0
Mat() {cls();}
friend Mat operator * (Mat a, Mat b)
{
Mat res;
for(int i = 0; i < maxn; i++) for(int j = 0; j < maxn; j++)
for(int k = 0; k < maxn; k++)
(res.f[i][j] += a.f[i][k] * b.f[k][j]) %= mod;
return res;
}
};
Mat quick_pow(Mat a)
{
Mat ans;
for(int i = 0; i < maxn; i++) ans.f[i][i] = 1;
int b = k;
while(b != 0)
{
if(b & 1) ans = ans * a;
b >>= 1;
a = a * a;
}
return ans;
}
应用
讲了矩阵快速幂之后,这个东西到底有什么用呢?
众所周知的斐波那契数列\(f_{n} = f_{n-1} + f_{n-2}\)就可以用矩阵快速幂优化。
\[\left[
\begin{matrix}
f_{n} & f_{n-1} \\
f_{n-1} & f_{n-2}
\end{matrix}
\right]
=
\left[
\begin{matrix}
f_{n-1} & f_{n-2} \\
f_{n-2} & f_{n-3}
\end{matrix}
\right]
\left[
\begin{matrix}
1 & 1 \\
1 & 0
\end{matrix}
\right]
=
\left[
\begin{matrix}
1 & 1 \\
1 & 0
\end{matrix}
\right]
^{n} \tag{1}
\]
(1)式就可以用矩阵快速幂在Olog(n)内算出来
习题
POJ 3070
HDOJ 1575
POJ 3233
HDOJ 2604
HDOJ 1757
ZOJ 3497
ZOJ 2853
总结
快速幂说复杂也挺抽象(对我这个蒟蒻来说),说简单也不难。
仔细体会其中后就能发现其中的思想很巧妙很漂亮,需要用心理解。
顺便一提,快速幂是快速算乘方运算的,过程中可以加上取模运算,实现题目要求,当然也可以不取模。不要误以为快速幂只是用来解决要取模的问题。
实际上,取模应当是快速幂中一个比较费时的计算,虽然我们只需要写一行代码。