Bài 57: Nhân Ma Trận & Lũy Thừa Ma Trận¶
Tác giả: FPTOJ Team
Nội dung tham khảo từ: CP-Algorithms, VNOI Wiki
1. Ma Trận Trong Thi Đấu¶
1.1 Tại sao cần nhân ma trận?¶
Nhiều bài toán có dạng truy hồi tuyến tính:
Ví dụ: Fibonacci: \(F(n) = F(n-1) + F(n-2)\), cần tính \(F(10^{18})\).
Đệ quy → quá chậm. Quy hoạch động → không đủ bộ nhớ. Lũy thừa ma trận giải quyết trong \(O(k^3 \log n)\).

1.2 Các ứng dụng phổ biến¶
- Tính số Fibonacci, tribonacci, ... cho \(n\) rất lớn
- Đếm số đường đi có độ dài \(k\) trong đồ thị
- Grid DP với số cột nhỏ nhưng số hàng rất lớn
- Truy hồi tuyến tính tổng quát
2. Nhân Ma Trận¶
2.1 Định nghĩa¶
Cho ma trận \(A\) kích thước \(n \times m\) và \(B\) kích thước \(m \times p\), tích \(C = A \times B\) có kích thước \(n \times p\):
Nghĩa là: để tính \(C[i][j]\) (hàng \(i\), cột \(j\)), ta lấy tích vô hướng của hàng thứ \(i\) của \(A\) với cột thứ \(j\) của \(B\).
2.2 Minh họa¶
2.3 Cài đặt¶
Trace tay từng \(C[i][j]\) với \(A = \begin{pmatrix}1&2&3\\4&5&6\end{pmatrix}\), \(B = \begin{pmatrix}7&8\\9&10\\11&12\end{pmatrix}\):
| Ô | Công thức | Tính từng bước | Kết quả |
|---|---|---|---|
| \(C[0][0]\) | \(1{\times}7 + 2{\times}9 + 3{\times}11\) | \(7 + 18 + 33\) | 58 |
| \(C[0][1]\) | \(1{\times}8 + 2{\times}10 + 3{\times}12\) | \(8 + 20 + 36\) | 64 |
| \(C[1][0]\) | \(4{\times}7 + 5{\times}9 + 6{\times}11\) | \(28 + 45 + 66\) | 139 |
| \(C[1][1]\) | \(4{\times}8 + 5{\times}10 + 6{\times}12\) | \(32 + 50 + 72\) | 154 |
Độ phức tạp: \(O(n \times m \times p)\) cho ma trận \(n \times m\) nhân \(m \times p\).
3. Lũy Thừa Ma Trận¶
3.1 Ý tưởng¶
Tương tự binary exponentiation cho số, ta tính \(A^b\) bằng cách "nhân đôi":
3.2 Cài đặt¶
Trace tay: tính \(M^5\) cho Fibonacci với \(M = \begin{pmatrix}1&1\\1&0\end{pmatrix}\), \(5 = 101_2\):
| Vòng | \(b\) (nhị phân) | Bit cuối | \(result\) | \(A\) |
|---|---|---|---|---|
| đầu | \(101\) | — | \(I\) | \(M^1\) |
| 1 | \(101\) | 1 → \(result = I \times M = M\) | \(M^1\) | \(A \leftarrow M^2 = \begin{pmatrix}2&1\\1&1\end{pmatrix}\); \(b = 10\) |
| 2 | \(10\) | 0 → bỏ qua | \(M^1\) | \(A \leftarrow M^4 = \begin{pmatrix}5&3\\3&2\end{pmatrix}\); \(b = 1\) |
| 3 | \(1\) | 1 → \(result = M \times M^4 = M^5 = \begin{pmatrix}8&5\\5&3\end{pmatrix}\) | \(M^5\) | dừng |
Kiểm tra: \(result[0][0] = 8 = F(6)\)? Với công thức \(M^{n-1}[0][0] = F(n)\): \(M^5[0][0] = 8 = F(6)\) ✓ (dãy \(0,1,1,2,3,5,8\)).
Độ phức tạp: \(O(k^3 \log b)\) cho ma trận \(k \times k\).
4. Ứng dụng 1: Fibonacci¶
4.1 Truy hồi¶
\(F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2)\)
4.2 Biến đổi thành ma trận¶
Suy ra:
4.3 Lỗi thường gặp (nhân & lũy thừa ma trận)¶
SAI — Nhân xong mới mod (tràn số):
C.a[i][j] = (C.a[i][j] + A.a[i][k] * B.a[k][j]) % MOD;
SAI — Đổi thứ tự nhân: Ma trận không giao hoán! multiply(result, A) khác multiply(A, result). Trong powerMatrix, khi bit \(=1\) phải viết result = multiply(result, A) (nhân bên phải), không được đảo thành multiply(A, result) — với Fibonacci \(2\times2\) đối xứng thì trùng cờ, nhưng ma trận chuyển tổng quát sẽ cho đáp án sai.
SAI — Quên ma trận đơn vị: Khởi tạo result bằng ma trận 0 thay vì \(I\) → mọi phép nhân sau đều ra 0. Luôn bắt đầu result = identityMatrix(n) (tương tự res = 1 trong lũy thừa số).
5. Ứng dụng 2: Truy hồi tuyến tính tổng quát¶
Cho truy hồi: \(f(n) = c_1 f(n-1) + c_2 f(n-2) + \cdots + c_k f(n-k)\)
Ma trận chuyển:
Hàng đầu tiên chứa các hệ số \(c_1, \ldots, c_k\) của truy hồi. Các hàng còn lại là hàng dịch — mỗi hàng chỉ có một số 1, đẩy giá trị xuống.
Lũy thừa \(n-k+1\) vì ta cần "nhảy" từ vector \((f(k-1), \ldots, f(0))\) đến \((f(n), \ldots, f(n-k+1))\), tức \(n-k+1\) bước.
Ví dụ: Tribonacci \(f(n) = f(n-1) + f(n-2) + f(n-3)\), \(k = 3\):
6. Ứng dụng 3: Đếm đường đi trong đồ thị¶
6.1 Bài toán¶
Cho đồ thị có \(n\) đỉnh, ma trận kề \(A\). Hỏi có bao nhiêu đường đi từ đỉnh \(u\) đến đỉnh \(v\) có đúng độ dài \(k\)?
6.2 Kết quả¶
\(A^k[u][v]\) = số đường đi từ \(u\) đến \(v\) có độ dài đúng \(k\).
7. Grid DP với số hàng lớn¶
7.1 Bài toán¶
Cho lưới \(n \times m\) (\(n\) rất lớn, \(m \leq 10\)). Mỗi ô có thể đi sang phải, xuống, hoặc chéo. Đếm số cách đi từ \((1, 1)\) đến \((n, m)\).
7.2 Giải¶
Với mỗi hàng, trạng thái là bitmask \(m\) bit → ma trận chuyển kích thước \(2^m \times 2^m\). Dùng lũy thừa ma trận để tính cho \(n\) hàng.