你正在开发一款 2D 游戏。角色站在坐标 (3, 4) 的位置,现在要让他逆时针旋转 90°,再沿 x 方向放大 2 倍。
最直接的做法:写一个函数 rotate(x, y) 算一次,再写一个 scale(x, y) 算一次。但如果这一帧要处理 10 万个物体,每个都要做 5 次变换呢?50 万次函数调用,性能立刻崩掉。
数学家的做法完全不同:把每种变换表示成一个 2×2 的矩阵。
旋转 90°:R = [[0, −1], [1, 0]];放大 2 倍:S = [[2, 0], [0, 1]]。把角色坐标写成列向量 v = (3, 4)T,一次矩阵乘法就完成旋转:
R·v = [[0,−1],[1,0]]·(3,4)ᵀ = (0·3 + (−1)·4, 1·3 + 0·4)ᵀ = (−4, 3)ᵀ
更妙的是:5 次变换可以先合并成 1 个矩阵。先算 M = S·R(矩阵乘法),之后每个物体只需一次乘法 M·v —— 从 5 次降到 1 次,快了 5 倍。
这套"把操作变成矩阵、把连续操作变成矩阵连乘"的思想,正是本讲的主线。而在 OI 里,它最经典的应用是:把 O(n) 的线性递推压缩成 O(log n) 的矩阵快速幂。
💡 故事背后的算法思想
矩阵 = 线性变换的载体。凡是"把一组数按固定系数线性组合成另一组数"的操作,都能写成矩阵乘法。这个"固定系数"就是矩阵的元素。
矩阵乘法 = 变换的复合。先做 A 再做 B,等价于做一次 B·A(注意顺序!)。这就是矩阵乘法结合律成立、交换律不成立的根本原因 —— 先旋转再放大,和先放大再旋转,结果通常不同。
矩阵快速幂 = 线性递推加速器。斐波那契 f(n) = f(n−1) + f(n−2),写成矩阵就是 (f(n), f(n−1))ᵀ = M·(f(n−1), f(n−2))ᵀ。反复代入 n 次得到 Mn,而幂可以用"平方累乘"在 O(log n) 次矩阵乘法内算出。这就是把 O(n) 降到 O(n³ log n)(n 是矩阵阶数,通常是常数)的核心技巧。