Content:矩阵 DP
Date:2025.7.27
Review
矩阵基本操作:link
Example1 - 洛谷-P1962 斐波那契数列
题目描述
给定 n,求斐波那契数列的第 n 项 fn。
fn={1fn−1+fn−2n=0,1oterwise
数据范围:n<263。
思路
首先显然 O(n) 的递推是不行的了,我们考虑将递推式转化为矩阵乘法的形式:
[fn−1fn−2]×[1011][fn−2fn−3]×[1011]2[f0f1]×[1011]n−2=[fnfn−1]=[fnfn−1]…=[fnfn−1]
这里只需要矩阵快速幂即可。复杂度 O(logn)。
提交记录:link
Example2 - UVA11270 Tiling Dominoes
题目描述
给定一个 n×m 的网格,求用 1×2 的方块覆盖网格的方案数。
数据范围:n×m≤100。
思路
我们考虑轮廓线 DP,定义 dpi,j,s 表示当前修改的点是 (i,j),其状态为 s。这里的 s 和之前题目里的不同,它表示的是第 i 行的前 j−1 列的覆盖情况和第 i−1 行的后 m−j+1 列的覆盖情况,其中 0 表示还未被覆盖,1 表示已经被覆盖。
dp 的第一维可以省略,所以转化为 dpi,s 表示考虑到第 j 列,其状态为 s 的方案数。
接下来考虑转移,设上一行的状态为 s,转移有三种。
- 当前位置不放,留空 (前提条件:s>>j & 1):dpi−1,s→dpi,s⊕(1<<j)
- 当前位置横着放 (前提条件:j>0 且 s>>j & 1):dpi−1,s→dpi,s∣(1<<(j−1))
- 当前位置竖着放 (前提条件:i>0 且 !(s>>j&1)):dpi−1,s⊕(1<<j)→dpi,s。
这样就写完了。
Code
#include <bits/stdc++.h>
using std::cin; using std::cout;
constexpr int N = 105; int n, m; long long dp[2][1 << 11];
int main() { std::ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); while (cin >> n >> m) { if (n < m) std::swap(n, m); int line = 0, max_status = 1 << m; std::memset(dp, 0, sizeof(dp)); dp[line][max_status - 1] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j < m; j++) { line = !line; std::memset(dp[line], 0, sizeof(dp[line])); for (int status = 0; status < max_status; status++) { if (status >> j & 1) { dp[line][status ^ (1 << j)] += dp[!line][status]; } if (j > 0 && (status >> (j - 1) & 1) == 0 && (status >> j & 1)) { dp[line][status | (1 << (j - 1))] += dp[!line][status]; } if (i > 0 && (status >> j & 1) == 0) { dp[line][status | (1 << j)] += dp[!line][status]; } } } } cout << dp[line][max_status - 1] << '\n'; } return 0; }
|
思路
我们一样考虑将递推式转化成矩阵乘法的形式。
但是这里我们注意到地推中的操作是 ∣ 和 &,所以哦我们要做 (∣,&) 矩阵乘法 (这个需要证明 ∣ 操作对 & 操作具有分配律,这里就不证明了)。
[an−1an−2an−3…an−k]×bn−1−10⋮0bn−20−1⋮0bn−300⋮0………⋱…b100⋮−1b000⋮0=[anan−1an−2…an−k+1]
所以就可以使用矩阵快速幂了。
提交记录:link。