考虑如下问题:计算a的b次方
当然这个结果可能会很大,哪怕是long long类型也存不下。所以我们对计算结果做一个约束,比如说后3位,即求a的b次方的后3位。
正常人的思路应该是一个for循环累乘,用代码展现就是下面这个样子:
int power(int a, int b)
{
//利用循环,这是最基础的写法,最慢
int sum = 1;
for (int i = 1; i <= b; i++)
sum = sum * a % mod;
return sum;
}
其中,mod是模数,可以根据需求取后几位。显然,这个方法的时间复杂度为 O ( b ) O(b) O(b)。当b很大的时候这个算法的性能就略显不足了。
怎么优化呢?这就要用到二分的思想了: a b a^{b} ab可以这样计算 ( a 2 ) b / 2 (a^{2})^{b/2} (a2)b/2,这样一来b的值就减小了一半,节约了一半的时间。如果底数继续平方,指数继续减半,这样就能把时间复杂度降到 O ( log 2 b ) O(\log_{2}{b} ) O(log2b)。当然这里有个情况,b不总是2的倍数。当b不是2的倍数的时候,特殊处理一下,单独拿出一个a乘到结果上就好了。代码如下:
int power1(int a, int b)
{
//快速幂,基本原理
int sum = 1;
while (b > 0)
{
if (b % 2 == 0)
{
//指数为偶数,底数平方一下,指数减半
a = a * a % mod;
b /= 2;
}
else
{
//指数为奇数,拿出一个底数,剩下的就是偶数次了,然后和上面同样操作
b--; //指数减一,变成偶数
sum = sum * a % mod;
a = a * a % mod;
b /= 2;
}
}
return sum;
}
这就是快速幂的基本原理了,不过上面这个写法还不够简洁,还不够快。观察上述代码可以发现,a = a * a % mod; b /= 2;
这两行代码出现了两次,这是完全没有必要的;还有判断奇偶、除以2可以用位运算来代替。优化后的代码如下:
int power2(int a, int b)
{
//终极写法,最快
int sum = 1;
while (b > 0)
{
if (b & 1) //等同于if(b % 2 == 1)
sum = sum * a % mod;
b >>= 1; //等同于b/=2
a = a * a % mod;
}
return sum;
}
矩阵快速幂和上面的思想是一样的,只不过上面都是整数的运算,这里进行的是矩阵的运算。矩阵乘法怎么运算参考线性代数。洛谷上有道矩阵快速幂的模板题,可以参考一下:https://www.luogu.com.cn/problem/P3390
下面给出矩阵快速幂的代码:
#include
#include
using namespace std;
int n;
const long long mod = 1e9 + 7;
struct arr
{
long long num[105][105];
arr()
{
memset(num, 0, sizeof(num));
}
};
arr multiply(arr a, arr b)
{
// 计算方阵a乘方阵b
arr res;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
res.num[i][j] = (res.num[i][j] + a.num[i][k] * b.num[k][j]) % mod;
return res;
}
arr power(arr a, long long k)
{
arr res;
for (int i = 0; i < n; i++) // 构造单位矩阵
res.num[i][i] = 1;
while (k > 0)
{
if (k & 1)
res = multiply(res, a);
k >>= 1;
a = multiply(a, a);
}
return res;
}
int main()
{
long long k;
arr a;
cin >> n >> k;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> a.num[i][j];
arr res = power(a, k);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
cout << res.num[i][j] << " ";
cout << endl;
}
return 0;
}
矩阵快速幂的一个简单应用就是斐波那契数列。斐波那契数列就是形如:1,1,2,3,5,8……的数列。它的数学描述如下:
F n = { 1 , n ≤ 2 F n − 1 + F n − 2 , n ≥ 3 F_{n}=\left\{\begin{matrix} 1&,n\le 2 \\ F_{n-1}+F_{n-2} &,n\ge 3 \end{matrix}\right. Fn={1Fn−1+Fn−2,n≤2,n≥3
斐波那契数列的增长是很快的,所以我们经常求模来限制结果的长度,例如 F n m o d 1 0 9 + 7 F_{n} \mod 10^{9}+7 Fnmod109+7。
求斐波那契数列第n项的一个朴素思路就是按照递推公式循环递推下去,其时间复杂度显然为 O ( n ) O(n) O(n)。然而如果能够利用快速幂的思想优化的话,就能够降到 O ( log 2 n ) O(\log_{2}{n} ) O(log2n)。
考虑斐波那契数列的递推公式为 F n = F n − 1 + F n − 2 F_{n}=F_{n-1}+F_{n-2} Fn=Fn−1+Fn−2,将其矩阵化的话每一项应该是像 [ F n − 1 F n − 2 ] \begin{bmatrix} F_{n-1}& F_{n-2} \end{bmatrix} [Fn−1Fn−2]这样的。那么,等式左边也应该变成这样 [ F n F n − 1 ] \begin{bmatrix} F_{n} & F_{n-1} \end{bmatrix} [FnFn−1]。问题就变成了找到一个矩阵 X X X使得 [ F n F n − 1 ] = [ F n − 1 F n − 2 ] ∗ X \begin{bmatrix} F_{n} &F_{n-1} \end{bmatrix}=\begin{bmatrix} F_{n-1} &F_{n-2} \end{bmatrix}*X [FnFn−1]=[Fn−1Fn−2]∗X成立。显然矩阵 X X X应该是两行两列的,即
[ F n F n − 1 ] = [ F n − 1 F n − 2 ] ∗ [ x 1 x 2 x 3 x 4 ] \begin{bmatrix} F_{n} &F_{n-1} \end{bmatrix}=\begin{bmatrix} F_{n-1} &F_{n-2} \end{bmatrix}*\begin{bmatrix} x_{1}&x_{2} \\ x_{3}&x_{4} \end{bmatrix} [FnFn−1]=[Fn−1Fn−2]∗[x1x3x2x4]
根据矩阵运算规则, { F n = x 1 F n − 1 + x 3 F n − 2 F n − 1 = x 2 F n − 1 + x 4 F n − 2 \left\{\begin{matrix} F_{n}=x_{1}F_{n-1}+x_{3}F_{n-2} \\ F_{n-1}=x_{2}F_{n-1}+x_{4}F_{n-2} \end{matrix}\right. {Fn=x1Fn−1+x3Fn−2Fn−1=x2Fn−1+x4Fn−2。显然, x 1 , x 2 , x 3 , x 4 x_{1},x_{2},x_{3},x_{4} x1,x2,x3,x4的值都能求出来。所以,矩阵 X = [ 1 1 1 0 ] X=\begin{bmatrix} 1& 1\\ 1&0 \end{bmatrix} X=[1110]。
根据矩阵化的递推公式 [ F n F n − 1 ] = [ F n − 1 F n − 2 ] ∗ [ 1 1 1 0 ] \begin{bmatrix} F_{n} &F_{n-1} \end{bmatrix}=\begin{bmatrix} F_{n-1} &F_{n-2} \end{bmatrix}*\begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix} [FnFn−1]=[Fn−1Fn−2]∗[1110],将等式右边不断展开, [ F n F n − 1 ] = [ F n − 2 F n − 3 ] ∗ [ 1 1 1 0 ] 2 = … … \begin{bmatrix} F_{n} &F_{n-1} \end{bmatrix}=\begin{bmatrix} F_{n-2} &F_{n-3} \end{bmatrix}*{\begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}}^{2}=…… [FnFn−1]=[Fn−2Fn−3]∗[1110]2=……
直到 [ F n F n − 1 ] = [ F 2 F 1 ] ∗ [ 1 1 1 0 ] n − 2 \begin{bmatrix} F_{n} &F_{n-1} \end{bmatrix}=\begin{bmatrix} F_{2} &F_{1} \end{bmatrix}*{\begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}}^{n-2} [FnFn−1]=[F2F1]∗[1110]n−2。此时 F 2 F_{2} F2和 F 1 F_{1} F1都是已知的,都为1,所以问题就变成了求右边矩阵的n-2次方。这就是上面矩阵快速幂的内容。
斐波那契数列在洛谷上也有模板题:https://www.luogu.com.cn/problem/P1962
AC代码如下:
#include
#include
using namespace std;
int n = 2; // 方阵阶数
const long long mod = 1e9 + 7; // 模数
struct arr
{
long long num[105][105];
arr()
{
memset(num, 0, sizeof(num));
}
};
arr multiply(arr a, arr b)
{
// 计算方阵a乘方阵b
arr res;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
res.num[i][j] = (res.num[i][j] + a.num[i][k] * b.num[k][j]) % mod;
return res;
}
arr power(arr a, long long k)
{
arr res;
for (int i = 0; i < n; i++) // 构造单位矩阵
res.num[i][i] = 1;
while (k > 0)
{
if (k & 1)
res = multiply(res, a);
k >>= 1;
a = multiply(a, a);
}
return res;
}
int main()
{
arr a;
long long x; // 第x项
cin >> x;
if (x < 3)
{
cout << 1 << endl;
return 0;
}
// 递推矩阵赋值
a.num[0][0] = 1;
a.num[0][1] = 1;
a.num[1][0] = 1;
a.num[1][1] = 0;
arr res = power(a, x - 2);
cout << (res.num[0][0] + res.num[1][0]) % mod << endl;
return 0;
}