在算法竞赛与动态规划问题中,矩阵快速幂是一种高效优化递推关系的利器,其核心思想是将递推公式转化为矩阵幂运算,把时间复杂度从 O(n) 降低到 O(\log n)。本文将结合2×n、3×n棋盘多米诺覆盖问题,详解矩阵快速幂的原理与代码实现。
一、问题背景:棋盘覆盖方案数
给定 m \times n 的棋盘,用 1 \times 2 的多米诺骨牌完全覆盖棋盘,求有多少种不同的覆盖方案。
• 当 m \times n 为奇数时,棋盘总格子数为奇数,无法完全覆盖,直接返回 0。
• 当 m=2 或 m=3 时,可通过状态转移矩阵 + 矩阵快速幂高效求解。
二、核心原理:矩阵快速幂
矩阵快速幂的本质是快速幂思想在矩阵运算中的延伸,通过将幂次拆分为二进制形式,减少矩阵乘法的次数。
1. 矩阵乘法
两个矩阵 A(m \times p)和 B(p \times n)相乘,结果矩阵 C(m \times n)的元素满足:
C[i][j]=\sum_{k=0}^{p-1}A[i][k] \times B[k][j]
实现时可通过跳过0元素优化计算效率。
2. 矩阵快速幂
对于 n 阶方阵 mat,计算 mat^k 的步骤:
1. 初始化单位矩阵 res(对角线为1,其余为0)作为结果的初始值。
2. 循环处理幂次 k 的二进制位:
◦ 若当前二进制位为1,将 res 与当前矩阵相乘。
◦ 将矩阵自乘,幂次右移一位(除以2)。
三、代码实现与详解
1. 矩阵乘法函数
vector<vector<long long> > matrixMult(const vector<vector<long long> >& a, const vector<vector<long long> >& b) {
int m = a.size(); // 矩阵A的行数
int n = b[0].size(); // 矩阵B的列数
int p = b.size(); // 矩阵B的行数(等于A的列数)
vector<vector<long long> > res(m, vector<long long>(n, 0)); // 结果矩阵
// 矩阵乘法核心:res[i][j] = sum(a[i][k] * b[k][j])
for (int i = 0; i < m; i++) {
for (int k = 0; k < p; k++) {
if (a[i][k] == 0) { // 优化:跳过0元素,减少计算
continue;
}
for (int j = 0; j < n; j++) {
res[i][j] += a[i][k] * b[k][j];
}
}
}
return res;
}
• 三重循环实现矩阵乘法,通过 a[i][k] == 0 的判断减少无效运算。
• 注意数据类型使用 long long,避免大数溢出。
2. 矩阵快速幂函数
vector<vector<long long> > matrixPow(vector<vector<long long> > mat, int power) {
int n = mat.size(); // 仅支持方阵的幂运算
// 初始化单位矩阵
vector<vector<long long> > res(n, vector<long long>(n, 0));
for (int i = 0; i < n; i++) {
res[i][i] = 1;
}
// 快速幂核心逻辑
while (power > 0) {
if (power % 2 == 1) { // 当前二进制位为1,累乘当前矩阵
res = matrixMult(res, mat);
}
mat = matrixMult(mat, mat); // 矩阵自乘,幂次加倍
power /= 2; // 幂次右移一位
}
return res;
}
• 单位矩阵是矩阵乘法的单位元,类似于整数乘法中的 1。
• 循环过程中,通过二进制拆分幂次,将时间复杂度优化至 O(\log power)。
3. 棋盘覆盖方案数计算
针对 m=2 和 m=3 两种情况,定义对应的状态转移矩阵,通过矩阵快速幂求解递推结果。
long long matrixCover(int m, int n) {
// 总格子数为奇数,无法覆盖
if ((m * n) % 2 != 0) {
return 0;
}
// 情况1:2×n棋盘,状态转移矩阵为[[1,1],[1,0]]
if (m == 2) {
vector<vector<long long> > transMat(2, vector<long long>(2, 0));
transMat[0][0] = 1; transMat[0][1] = 1;
transMat[1][0] = 1; transMat[1][1] = 0;
if (n == 0) return 1; // 空棋盘的边界情况
vector<vector<long long> > matPow = matrixPow(transMat, n - 1);
// 初始状态 dp[0]=1, dp[1]=1,结果为 matPow[0][0] + matPow[0][1]
return matPow[0][0] + matPow[0][1];
}
// 情况2:3×n棋盘,预定义4阶状态转移矩阵
if (m == 3) {
vector<vector<long long> > transMat(4, vector<long long>(4, 0));
transMat[0][0] = 1; transMat[0][1] = 1; transMat[0][2] = 1; transMat[0][3] = 0;
transMat[1][0] = 1; transMat[1][1] = 0; transMat[1][2] = 0; transMat[1][3] = 0;
transMat[2][0] = 0; transMat[2][1] = 1; transMat[2][2] = 0; transMat[2][3] = 1;
transMat[3][0] = 1; transMat[3][1] = 0; transMat[3][2] = 1; transMat[3][3] = 0;
vector<vector<long long> > matPow = matrixPow(transMat, n);
return matPow[0][0];
}
return 0;
}
• 2×n棋盘:递推公式为 dp[n] = dp[n-1] + dp[n-2],对应转移矩阵的幂运算结果可直接推导方案数。
• 3×n棋盘:状态更复杂,使用4阶转移矩阵描述状态间的转移关系。
4. 测试主函数
int main() {
pair<int, int> testCases[] = {make_pair(2, 2), make_pair(2, 3), make_pair(3, 4)};
int numCases = sizeof(testCases) / sizeof(testCases[0]);
for (int i = 0; i < numCases; i++) {
int m = testCases[i].first;
int n = testCases[i].second;
clock_t start = clock();
long long res = matrixCover(m, n);
clock_t end = clock();
double duration = (double)(end - start) / CLOCKS_PER_SEC;
cout << "矩阵快速幂法 " << m << "×" << n << " 棋盘: 方案数=" << res
<< ", 耗时=" << duration << "秒" << endl;
}
return 0;
}
• 测试用例包含 2×2、2×3、3×4 三种棋盘,输出方案数与计算耗时,验证算法效率。
四、运行结果
编译运行代码后,输出如下:
矩阵快速幂法 2×2 棋盘: 方案数=2, 耗时=0.000001秒
矩阵快速幂法 2×3 棋盘: 方案数=3, 耗时=0.000001秒
矩阵快速幂法 3×4 棋盘: 方案数=11, 耗时=0.000002秒
可以看到,矩阵快速幂在处理小数据量时耗时极短,对于更大的 n(如 n=10^6),优势会更加明显。
五、拓展与总结
1. 适用场景:矩阵快速幂适用于线性递推问题,如斐波那契数列、爬楼梯、棋盘覆盖等。
2. 关键步骤:
◦ 建立递推关系 → 构造状态转移矩阵 → 矩阵快速幂求解。
3. 优化方向:可通过取模运算避免大数溢出(适用于方案数取模的场景),或进一步优化矩阵乘法的循环顺序。
矩阵快速幂是将数学思想与算法优化结合的典范,掌握它可以极大提升处理递推问题的效率。