Lights Out 与矩阵
· 3 分钟
#数学
Lights Out 游戏规则
Lights Out 是一款经典的逻辑谜题游戏。游戏面板是一个 的方格网格,每个方格是一个灯泡,初始状态为亮(1)或灭(0)。点击任意一个灯泡,会切换该灯泡及其上下左右四个相邻灯泡的状态(亮变灭,灭变亮)。
目标是让所有灯泡都熄灭。
问题的数学建模
将每个灯泡的状态表示为 (模 2 域)上的变量:
- :是否点击位置 的灯泡(1 表示点击)
- :位置 的初始状态
点击某个灯泡产生的效果可以用一个邻接矩阵来描述。对于 的面板,我们将其展平为 25 个变量:
线性方程组可以写成:
其中 是一个 的矩阵, 当且仅当点击 会影响灯泡 。
矩阵示例
对于 的简化面板,邻接矩阵为:
行向量与列向量
初始状态向量 也是一个列向量:
解向量 同样是列向量:
在 上的高斯消元
由于所有运算都在 上进行(加法即 XOR),我们可以使用高斯消元法求解:
import numpy as np
def gf2_gaussian_elimination(A, b):
"""
在 GF(2) 域上求解线性方程组 Ax = b
参数:
A: 方阵 (n×n), 元素为 0/1
b: 右端项 (n,), 元素为 0/1
返回:
如果方程有解,返回一个特解 x (自由变量取 0);
如果无解,返回 None。
"""
n = len(b)
# 增广矩阵 [A | b]
M = np.hstack([A, b.reshape(-1, 1)]).astype(np.uint8)
pivot_cols = [] # 记录每个主元所在的列号
row = 0 # 当前处理到的行(也就是秩)
# 逐列进行消元
for col in range(n):
# 在当前列中,从 row 行开始寻找第一个为 1 的元素作为主元
pivot = None
for r in range(row, n):
if M[r, col] == 1:
pivot = r
break
# 如果该列全部为零,则是自由变量,直接进入下一列
if pivot is None:
continue
# 将主元所在行交换到当前 row 行
if pivot != row:
M[[row, pivot]] = M[[pivot, row]]
# Jordan 消元:将其他所有行的第 col 列都消为 0
for r in range(n):
if r != row and M[r, col] == 1:
M[r] ^= M[row]
# 记录该主元对应的列号,并增加已处理行数
pivot_cols.append(col)
row += 1
# 检查是否存在矛盾方程:系数全为零,但右端项为 1
for r in range(row, n):
if M[r, n] == 1:
return None # 无解
# 构造特解:自由变量默认为 0
x = np.zeros(n, dtype=np.uint8)
# 对于每个主元行,其对应的主元变量可以直接从右端项读出
for r, c in enumerate(pivot_cols):
x[c] = M[r, n]
return x
矩阵乘法验证
对于任意解 ,验证是否满足原始方程:
如果矩阵 的秩为 ,则解空间维数为 。当 时,解唯一。
内积与正交性
在 上定义内积:
两个解向量之间的差 属于 的零空间:
可解性
对于 Lights Out,一个有趣的结论是:当且仅当 与 的左零空间正交时,方程组有解。
用公式表示:
实际游戏中的随机矩阵
除了标准 Lights Out,我们还可以生成随机谜题。对于一个随机生成的初始状态 ,其可解概率取决于 的秩。
结语
通过将 Lights Out 建模为 上的线性系统,我们可以用矩阵运算和线性代数理论完全分析这个游戏。这展示了数学在游戏设计中的强大应用——将一个看似复杂的谜题转化为优雅的代数问题。