乘法快速幂用于求一个数的高次方,矩阵快速幂用于求一个方阵的高次方。
固定关系的1维k阶递推表达式,用矩阵快速幂求解时间复杂度O(logn * k的3次方)
固定关系的k维1阶递推表达式,用矩阵快速幂求解时间复杂度O(logn * k的3次方)
下面通过题目加深理解。
题目一
测试链接:P1226 【模板】快速幂 - 洛谷
分析:对于次方进行二进制判断,如果当前位是1,则结果乘上当前算子,否则结果不变,每次判断后次方右移一位直到为0,算子最开始是底数,每次循环后,算子更新为自身的平方。代码如下。
#include <iostream>
using namespace std;
int main(void){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
int ans = 1;
int temp = a;
for(int i = 0;i < 32;++i){
if((b & (1 << i)) != 0){
ans = (int)(((long long)ans * temp) % c);
}
temp = (int)(((long long)temp * temp) % c);
}
printf("%d^%d mod %d=%d", a, b, c, ans);
return 0;
}
题目二
测试链接:509. 斐波那契数 - 力扣(LeetCode)
分析:有了之前乘法快速幂的经验,就可以写出矩阵快速幂,对于动态规划中的状态转移方程,可以利用矩阵快速幂加速运算。对于斐波那契数列,f(n) = f(n-1) + f(n-2),那么已知的第1位和第0位就是{1, 0},然后可以乘上{{1, 1}, {1, 0}},得到的{1, 1}的第一个数就是f(2),所以要求f(n)就是{1, 0}乘上{{1, 1}, {1, 0}}的n-1次方结果的第一个数。代码如下。
class Solution {
public:
vector<vector<int>> matrix_mul(vector<vector<int>> m1, vector<vector<int>> m2){
int row = m1.size();
int column = m2[0].size();
int middle = m1[0].size();
vector<vector<int>> ans;
ans.reserve(row);
for(int i = 0;i < row;++i){
vector<int> temp;
temp.resize(column);
for(int j = 0;j < column;++j){
temp[j] = 0;
for(int k = 0;k < middle;++k){
temp[j] += m1[i][k] * m2[k][j];
}
}
ans.push_back(temp);
}
return ans;
}
vector<vector<int>> power(vector<vector<int>> matrix, int num){
int length = matrix.size();
vector<vector<int>> ans;
ans.reserve(length);
for(int i = 0;i < length;++i){
vector<int> temp;
temp.resize(length);
temp[i] = 1;
ans.push_back(temp);
}
for(;num != 0;num >>= 1){
if((num & 1) != 0){
ans = matrix_mul(ans, matrix);
}
matrix = matrix_mul(matrix, matrix);
}
return ans;
}
int fib(int n) {
if(n == 0){
return 0;
}
if(n == 1){
return 1;
}
vector<vector<int>> com;
vector<int> temp1;
temp1.push_back(1);
temp1.push_back(1);
vector<int> temp2;
temp2.push_back(1);
temp2.push_back(0);
com.push_back(temp1);
com.push_back(temp2);
vector<vector<int>> start;
vector<int> temp3;
temp3.push_back(1);
temp3.push_back(0);
start.push_back(temp3);
return (matrix_mul(start, power(com, n-1)))[0][0];
}
};
题目三
分析:这道题和上一题基本一致,只是初始条件变了。代码如下。
class Solution {
public:
vector<vector<int>> matrix_mul(vector<vector<int>> m1, vector<vector<int>> m2){
int row = m1.size();
int column = m2[0].size();
int middle = m1[0].size();
vector<vector<int>> ans;
ans.reserve(row);
for(int i = 0;i < row;++i){
vector<int> temp;
temp.resize(column);
for(int j = 0;j < column;++j){
temp[j] = 0;
for(int k = 0;k < middle;++k){
temp[j] += m1[i][k] * m2[k][j];
}
}
ans.push_back(temp);
}
return ans;
}
vector<vector<int>> power(vector<vector<int>> matrix, int num){
int length = matrix.size();
vector<vector<int>> ans;
ans.reserve(length);
for(int i = 0;i < length;++i){
vector<int> temp;
temp.resize(length);
temp[i] = 1;
ans.push_back(temp);
}
for(;num != 0;){
if((num & 1) != 0){
ans = matrix_mul(ans, matrix);
}
num >>= 1;
if(num == 0){
break;
}
matrix = matrix_mul(matrix, matrix);
}
return ans;
}
int climbStairs(int n) {
if(n == 1){
return 1;
}
if(n == 2){
return 2;
}
if(n == 3){
return 3;
}
vector<vector<int>> com;
vector<int> temp1;
temp1.push_back(1);
temp1.push_back(1);
vector<int> temp2;
temp2.push_back(1);
temp2.push_back(0);
com.push_back(temp1);
com.push_back(temp2);
vector<vector<int>> start;
vector<int> temp3;
temp3.push_back(1);
temp3.push_back(1);
start.push_back(temp3);
return (matrix_mul(start, power(com, n-1)))[0][0];
}
};
题目四
测试链接:1137. 第 N 个泰波那契数 - 力扣(LeetCode)
分析:和之前两道题一样,都是1维k阶的递推。代码如下。
class Solution {
public:
vector<vector<int>> matrix_mul(vector<vector<int>> m1, vector<vector<int>> m2){
int row = m1.size();
int column = m2[0].size();
int middle = m1[0].size();
vector<vector<int>> ans;
ans.reserve(row);
for(int i = 0;i < row;++i){
vector<int> temp;
temp.resize(column);
for(int j = 0;j < column;++j){
temp[j] = 0;
for(int k = 0;k < middle;++k){
temp[j] += m1[i][k] * m2[k][j];
}
}
ans.push_back(temp);
}
return ans;
}
vector<vector<int>> power(vector<vector<int>> matrix, int num){
int length = matrix.size();
vector<vector<int>> ans;
ans.reserve(length);
for(int i = 0;i < length;++i){
vector<int> temp;
temp.resize(length);
temp[i] = 1;
ans.push_back(temp);
}
for(;num != 0;){
if((num & 1) != 0){
ans = matrix_mul(ans, matrix);
}
num >>= 1;
if(num == 0){
break;
}
matrix = matrix_mul(matrix, matrix);
}
return ans;
}
int tribonacci(int n) {
if(n == 0){
return 0;
}
if(n == 1){
return 1;
}
if(n == 2){
return 1;
}
vector<vector<int>> com;
vector<int> temp1;
temp1.push_back(1);
temp1.push_back(1);
temp1.push_back(0);
vector<int> temp2;
temp2.push_back(1);
temp2.push_back(0);
temp2.push_back(1);
vector<int> temp3;
temp3.push_back(1);
temp3.push_back(0);
temp3.push_back(0);
com.push_back(temp1);
com.push_back(temp2);
com.push_back(temp3);
vector<vector<int>> start;
vector<int> temp4;
temp4.push_back(1);
temp4.push_back(1);
temp4.push_back(0);
start.push_back(temp4);
return (matrix_mul(start, power(com, n-2)))[0][0];
}
};
题目五
测试链接:790. 多米诺和托米诺平铺 - 力扣(LeetCode)
分析:这道题首先得知道动态规划怎么解,然后可以通过解出的答案找到规律,递归可以是从i下标开始,有无凸出来的部分,向后递归。打表找到的规律就是f(n) = 2 * f(n-1) + f(n-3),f(1) = 1,f(2) = 2, f(3) = 5,知道这些就和之前的题一样了。代码如下。
class Solution {
public:
int MOD = 1000000007;
vector<vector<int>> matrix_mul(vector<vector<int>> m1, vector<vector<int>> m2){
int row = m1.size();
int column = m2[0].size();
int middle = m1[0].size();
vector<vector<int>> ans;
ans.reserve(row);
for(int i = 0;i < row;++i){
vector<int> temp;
temp.resize(column);
for(int j = 0;j < column;++j){
temp[j] = 0;
for(int k = 0;k < middle;++k){
temp[j] = (int)((temp[j] + (long long)m1[i][k] * m2[k][j]) % MOD);
}
}
ans.push_back(temp);
}
return ans;
}
vector<vector<int>> power(vector<vector<int>> matrix, int num){
int length = matrix.size();
vector<vector<int>> ans;
ans.reserve(length);
for(int i = 0;i < length;++i){
vector<int> temp;
temp.resize(length);
temp[i] = 1;
ans.push_back(temp);
}
for(;num != 0;){
if((num & 1) != 0){
ans = matrix_mul(ans, matrix);
}
num >>= 1;
if(num == 0){
break;
}
matrix = matrix_mul(matrix, matrix);
}
return ans;
}
int numTilings(int n) {
if(n == 1){
return 1;
}
if(n == 2){
return 2;
}
if(n == 3){
return 5;
}
vector<vector<int>> com;
vector<int> temp1;
temp1.push_back(2);
temp1.push_back(1);
temp1.push_back(0);
vector<int> temp2;
temp2.push_back(0);
temp2.push_back(0);
temp2.push_back(1);
vector<int> temp3;
temp3.push_back(1);
temp3.push_back(0);
temp3.push_back(0);
com.push_back(temp1);
com.push_back(temp2);
com.push_back(temp3);
vector<vector<int>> start;
vector<int> temp4;
temp4.push_back(5);
temp4.push_back(2);
temp4.push_back(1);
start.push_back(temp4);
return (matrix_mul(start, power(com, n-3)))[0][0];
}
};
题目六
测试链接:1220. 统计元音字母序列的数目 - 力扣(LeetCode)
分析:也是首先知道动态规划怎么解,对于dp[i][j]代表长度为i之前最后一个字母为j(这里可以给字母编号),所以最终结果是将需要的长度的那一行加起来,故这是一个k维1阶递推。找到递推关系式后,写成矩阵的形式就是{{0, 1, 0, 0, 0}, {1, 0, 1, 0, 0}, {1, 1, 0, 1, 1}, {0, 0, 1, 0, 1}, {1, 0, 0, 0, 0}},最开始长度为1时都可以取所以开始是{1, 1, 1, 1, 1},所以求长度为n就是开始乘矩阵的n-1次方的结果之和。代码如下。
class Solution {
public:
int MOD = 1000000007;
vector<vector<int>> matrix_mul(vector<vector<int>> m1, vector<vector<int>> m2){
int row = m1.size();
int column = m2[0].size();
int middle = m1[0].size();
vector<vector<int>> ans;
ans.reserve(row);
for(int i = 0;i < row;++i){
vector<int> temp;
temp.resize(column);
for(int j = 0;j < column;++j){
temp[j] = 0;
for(int k = 0;k < middle;++k){
temp[j] = (int)((temp[j] + (long long)m1[i][k] * m2[k][j]) % MOD);
}
}
ans.push_back(temp);
}
return ans;
}
vector<vector<int>> power(vector<vector<int>> matrix, int num){
int length = matrix.size();
vector<vector<int>> ans;
ans.reserve(length);
for(int i = 0;i < length;++i){
vector<int> temp;
temp.resize(length);
temp[i] = 1;
ans.push_back(temp);
}
for(;num != 0;){
if((num & 1) != 0){
ans = matrix_mul(ans, matrix);
}
num >>= 1;
if(num == 0){
break;
}
matrix = matrix_mul(matrix, matrix);
}
return ans;
}
void build(vector<vector<int>>& com, vector<vector<int>>& start){
vector<int> temp1;
temp1.resize(5);
temp1[0] = 0;
temp1[1] = 1;
temp1[2] = 0;
temp1[3] = 0;
temp1[4] = 0;
vector<int> temp2;
temp2.resize(5);
temp2[0] = 1;
temp2[1] = 0;
temp2[2] = 1;
temp2[3] = 0;
temp2[4] = 0;
vector<int> temp3;
temp3.resize(5);
temp3[0] = 1;
temp3[1] = 1;
temp3[2] = 0;
temp3[3] = 1;
temp3[4] = 1;
vector<int> temp4;
temp4.resize(5);
temp4[0] = 0;
temp4[1] = 0;
temp4[2] = 1;
temp4[3] = 0;
temp4[4] = 1;
vector<int> temp5;
temp5.resize(5);
temp5[0] = 1;
temp5[1] = 0;
temp5[2] = 0;
temp5[3] = 0;
temp5[4] = 0;
com.push_back(temp1);
com.push_back(temp2);
com.push_back(temp3);
com.push_back(temp4);
com.push_back(temp5);
vector<int> temp6;
temp6.resize(5);
temp6[0] = 1;
temp6[1] = 1;
temp6[2] = 1;
temp6[3] = 1;
temp6[4] = 1;
start.push_back(temp6);
}
int countVowelPermutation(int n) {
vector<vector<int>> com;
vector<vector<int>> start;
build(com, start);
vector<vector<int>> ans = matrix_mul(start, power(com, n-1));
int sum = 0;
for(int i = 0;i < 5;++i){
sum = (int)(((long long)sum + ans[0][i]) % MOD);
}
return sum;
}
};
题目七
测试链接:552. 学生出勤记录 II - 力扣(LeetCode)
分析:也是先解动态规划,递归函数有两个参数,之前缺勤天数,以当前天为结尾的连续迟到天数,因为这两个参数表示的状态有限,可以用编号表示状态,将二维参数降成一维参数,推导出递推关系式,写出矩阵,开始情况,之后和上一题基本相同。代码如下。
class Solution {
public:
int MOD = 1000000007;
vector<vector<int>> matrix_mul(vector<vector<int>> m1, vector<vector<int>> m2){
int row = m1.size();
int column = m2[0].size();
int middle = m1[0].size();
vector<vector<int>> ans;
ans.reserve(row);
for(int i = 0;i < row;++i){
vector<int> temp;
temp.resize(column);
for(int j = 0;j < column;++j){
temp[j] = 0;
for(int k = 0;k < middle;++k){
temp[j] = (int)((temp[j] + (long long)m1[i][k] * m2[k][j]) % MOD);
}
}
ans.push_back(temp);
}
return ans;
}
vector<vector<int>> power(vector<vector<int>> matrix, int num){
int length = matrix.size();
vector<vector<int>> ans;
ans.reserve(length);
for(int i = 0;i < length;++i){
vector<int> temp;
temp.resize(length);
temp[i] = 1;
ans.push_back(temp);
}
for(;num != 0;){
if((num & 1) != 0){
ans = matrix_mul(ans, matrix);
}
num >>= 1;
if(num == 0){
break;
}
matrix = matrix_mul(matrix, matrix);
}
return ans;
}
void build(vector<vector<int>>& com, vector<vector<int>>& start){
vector<int> temp1;
temp1.resize(6);
temp1[0] = 1;
temp1[1] = 1;
temp1[2] = 0;
temp1[3] = 1;
temp1[4] = 0;
temp1[5] = 0;
vector<int> temp2;
temp2.resize(6);
temp2[0] = 1;
temp2[1] = 0;
temp2[2] = 1;
temp2[3] = 1;
temp2[4] = 0;
temp2[5] = 0;
vector<int> temp3;
temp3.resize(6);
temp3[0] = 1;
temp3[1] = 0;
temp3[2] = 0;
temp3[3] = 1;
temp3[4] = 0;
temp3[5] = 0;
vector<int> temp4;
temp4.resize(6);
temp4[0] = 0;
temp4[1] = 0;
temp4[2] = 0;
temp4[3] = 1;
temp4[4] = 1;
temp4[5] = 0;
vector<int> temp5;
temp5.resize(6);
temp5[0] = 0;
temp5[1] = 0;
temp5[2] = 0;
temp5[3] = 1;
temp5[4] = 0;
temp5[5] = 1;
vector<int> temp6;
temp6.resize(6);
temp6[0] = 0;
temp6[1] = 0;
temp6[2] = 0;
temp6[3] = 1;
temp6[4] = 0;
temp6[5] = 0;
com.push_back(temp1);
com.push_back(temp2);
com.push_back(temp3);
com.push_back(temp4);
com.push_back(temp5);
com.push_back(temp6);
vector<int> temp7;
temp7.resize(6);
temp7[0] = 1;
temp7[1] = 1;
temp7[2] = 0;
temp7[3] = 1;
temp7[4] = 0;
temp7[5] = 0;
start.push_back(temp7);
}
int checkRecord(int n) {
vector<vector<int>> com;
vector<vector<int>> start;
build(com, start);
vector<vector<int>> ans = matrix_mul(start, power(com, n-1));
int sum = 0;
for(int i = 0;i < 6;++i){
sum = (int)(((long long)sum + ans[0][i]) % MOD);
}
return sum;
}
};