跳到主要内容

棋盘与逻辑谜题

图论、约束求解与谜题的数学

周六 1月 17 2026
41222 字 · 143 分钟

棋盘与逻辑谜题图鉴

棋牌、纸笔谜题和推理游戏常被当成三个品类,从计算机的角度看其实是同一件事:棋盘是一张图,谜题是一组约束,胜负或唯一解就是图上或约束系统里的一个目标。本文由八篇长文合并而成:棋类难度与规则内核的图论解释,欧拉路与一笔画、数回数桥,统一逻辑谜题家族的约束求解器,演绎推理的机械化,以及六种纸笔谜题的记账术、数独的出题机器和 2048、扫雷、点灯、滑块里的四件数学工具。每一节都有站内可玩的版本,建议边读边开一局。

五款棋牌,五种难度来源

有一类游戏几百年没怎么变过:一块板,几枚子,一套公开的规则。它们常被归到棋牌里,其实从计算机角度看是同一个东西——一个状态图,加一张合法的走法表。胜负只是图上的终止节点,策略就是在这张图上找一条通往终止节点的路径。

这么一看,深度立刻分裂成好几种互不相干的来源:状态空间有多大,信息是否完全,动作可逆不可逆,有没有能用的启发式。国际象棋难在分支多,跳棋难在必须穷举到底,推箱子难在不可逆,桥牌难在看不全,滑块难在一个藏起来的不变量。

形式化只要三个对象。状态 ss,当前局面的完整描述。走法 a∈A(s)a \in A(s),从 ss 出发的合法动作。终局判定,某个状态集合上的胜负或平局条件。

状态里要不要装历史,是下棋和推箱子的关键差别。下象棋你不需要知道怎么走过来的,推箱子你必须知道,因为箱子可能已经被推到没法回头的位置。

下面这张表是后面每一小节的底稿。

游戏状态数量级信息动作可逆难在哪
国际象棋104410^{44} 局面,1012010^{120} 博弈树完全部分分支因子 b≈35b \approx 35
西洋跳棋5×10205 \times 10^{20}完全部分必须穷举到底
推箱子指数级,判定 PSPACE 完全完全否不可逆带来的死锁
滑块16!/2≈101316!/2 \approx 10^{13}完全是不变量限制可达性
桥牌牌型组合 (5213)\binom{52}{13} 量级不完全否信息得靠叫牌换

象棋,把算得深变成能执行

Shannon 数 1012010^{120},分支因子 35。直接搜 10 层是 3510≈2.8×101535^{10} \approx 2.8 \times 10^{15} 个节点,任何机器都算不完。所以引擎的全部技术,都是在同一个搜索上做减法。

第一个减法是极小极大。我方行动取最大值,对方取最小值,交替递归。它把「我该怎么走」翻译成「假设双方都最优,这个局面值多少分」。第二个是 α-β 剪枝,某个分支已经不可能影响上层决策,整棵子树直接扔掉。

上面的演示用的是一棵深度 4 的完全二叉树,左右两侧分别是无剪枝和有剪枝的节点访问序列。理论上 α-β 在最优走法顺序下能把搜索量从 bdb^d 压到 bd/2b^{d/2},同样的时间,深度翻倍。这就是走法排序为什么是引擎里最值钱的工程细节,先试吃子,先试置换表里评分高的,剪枝效率完全取决于你是不是先撞见足够好的分支。

第三个减法其实不是减,是换。搜到叶节点必须给局面打分,标准配方是子力价值加位置表,兵 1、马和象 3、车 5、后 9,同一枚子在中心和在角落分数不同。

评估必然有误差,误差会带来地平线效应。引擎在第 dd 层看到一个好结果,看不见第 d+1d+1 层的反击,于是兴高采烈走进陷阱。工程补丁是静止搜索,叶节点上继续展开吃子序列,直到局面安静下来。

本站的国际象棋是双人、快棋、人机三合一。人机模式没搬整套引擎进去,只是遍历所有合法走法,用一个启发式分数取最大落子。它只算一层。这句话的价值在于提供了一个诚实的标尺,能赢它,说明你在算两步;赢不了它,说明你的失误在一步之内。轻量实现不该被包装成 AI 对手,它更像一个不会犯低级错误的陪练。

跳棋,被穷举证明的那一类

西洋跳棋规则比象棋简单得多,斜走、跳吃、到底线升王。但简单不等于小,它的状态空间仍有 5×10205 \times 10^{20},只是比象棋小了二十几个数量级。

这个量级刚好落在人类能硬啃的范围内。2007 年加拿大团队用 Chinook 宣布,双方都走最优时西洋跳棋是和棋。这项结果的分量不在程序下得比人好,而在对整张状态图的完全求解。Chinook 1990 年拿下美国冠军,1994 年的世界冠军赛因对手健康原因退出而接任,之后又花了十多年把剩余分支逐一反证成和棋。

跳棋可解的关键在强制吃子。有吃必吃意味着大量分支被砍掉,分支因子远小于象棋。代价是深度,很多局面的最优解要走二十步以上才见分晓。这也画出了穷举和启发式的分界,分支少、深度大,适合穷举;分支多、深度浅,适合评估。

推箱子,不可逆才是难点

推箱子看着是空间谜题,实际是这五款里理论上最难的一个,它的判定问题是 PSPACE 完全的。这个结论得理解准确。它不等于还没人找到多项式算法,它说的是如果真的存在多项式算法,PSPACE 就等于 P,计算复杂性理论得整体重写。通用解法基本不用指望。

难点的机制是动作不可逆,箱子只能推,不能拉。状态图是有向的,走错一步没法原路退回,只能在有限区域里绕圈。于是求解器的性能几乎全押在死锁检测上。

箱子被推进墙角,两面都是墙,永久死锁。两个箱子并排贴墙,谁也推不动,永久死锁。箱子占了某个目标格外侧,位置看着空闲,却挡死了另一个箱子的必经之路,这叫空间死锁。

判断都是局部模式匹配,代价极低,收益极大。在搜索早期砍掉死锁分支,比任何启发式都来得快。

人类玩家用的是另一套。从目标格逆推,给每个箱子分配目标,这是二分图匹配问题,可以用匈牙利算法最小化总距离,再把绕到箱子背后抽象成宏动作。

本站的推箱子只给关卡和提示,不内置求解器。这不是偷懒。求解器对玩家没价值,它是验证关卡可解性的工具,出题端才需要它。

滑块,一半的排列永远解不出来

滑块是 15-puzzle 的经典形态,4×4 格子,15 块拼图,一个空格。状态数看起来是 16!≈2.1×101316! \approx 2.1 \times 10^{13},真正可达的只有一半。

判据只有两行算术。把除空格外所有数字按行优先读成序列,数逆序对的个数。再看空格所在行号从底部数起的奇偶性。两者奇偶相同即可达,不同则不可达,于是只剩 16!/2≈1.05×101316!/2 \approx 1.05 \times 10^{13}。

这条不变量带来两个直接结果。随便打乱一个棋盘,有一半概率是无解的,怎么试都试不出来,不是你菜,是数学不允许。把两块拼图拆下来互换再装回去,物理上完全可行,逻辑上就是不可达状态。物理可逆和逻辑可达是两件事,这是不变量类谜题最漂亮的地方。

顺带澄清一个常见混淆。本站另有九宫格切图,那是把一张图按 Z 序切成 3×3 的九张,好在社交平台拼成长图,做的是图像切片。名字像,机制无关。

桥牌,压缩编码的问题

前四款的信息都完全,你和对手看到同一局面,胜负只取决于谁算得深。桥牌打破了这个前提。你只看得见自己 13 张牌,另外 39 张的分布要靠叫牌一点点换出来。

先看编码。一手牌的强度用一个四档计点法压成一个数字,A 记 4 点、K 记 3 点、Q 记 2 点、J 记 1 点,一副牌共 40 点。这个 4-3-2-1 的权重不是拍脑袋,它大致对应各张大牌在双明手分析里的实际赢墩贡献。于是「我这手牌有多强」从 13 张牌的形状,压缩成了一个 0 到 37 的整数。

叫牌体系就是长在这个标量上的决策树。标准美式黄卡的开叫逻辑可以完整写成 if-else,不足 12 点 pass;12 点以上且有五张以上高花,叫 1H 或 1S;没有五张高花但有四张以上低花,叫 1D 或 1C;20 点以上的均衡强牌走 2C 强开叫。本站的桥牌练习器实现的就是这棵树,它不评估你的牌感,只检查开叫落在不在规则表上。

所以桥牌的难度不在推理,在编码。你和搭档共享一套码本,每次叫牌花掉一个叫品,大约 4-5 比特,换对方牌型的一部分信息。叫得越省,能问的越多;叫得越准,越可能在对手干扰前定约。信息论上这是一个有噪声信道的压缩传输问题,而记体系的成本,就是这套编码的字典体积。

心流打字,对手是熵

最后补一个不在表里的异类,打字机心流模式。规则是停下之后,已经打出的文字开始淡出消失。没有对手,没有棋盘,没有终局判定。

但它同样是状态加时间预算的游戏,只是预算方向反过来,是负时间预算。正常游戏里时间是资源,你花时间换分数;这里时间是敌人,不产出就损失存量。它的设计目标是把拖延这个抽象问题变成可见的物理后果。写作者卡住的原因,往往是一直在改第一句,那这个模式干脆取消了回头修改的机会。

从博弈论看,它退化成单人游戏。从行为设计看,它是一次承诺装置,先自愿接受一个约束,让未来的自己没法反悔。这类机制现实里到处都是,把手机交给朋友,定一个不可退款的截止日,公开宣布目标,本质上都在给自己装一个淡出计时器。

练法也跟着分开。想练计算深度,去国际象棋解战术题,一道题胜过十盘随手棋。想练逆向规划,去推箱子从目标格倒推。想练不变量思维,去滑块数逆序对。想练信息压缩,去桥牌练习器把一手牌叫清楚。

把棋盘当图读,能看清难度从哪来。下一节反过来拆规则本身:四个具体棋盘游戏的源码里没有一行「棋」,全是图。

棋盘上的拓扑:把规则编译成图

把棋盘看成图,棋类规则就很好解释。落子是在图上添加顶点,移动是在图上找路径,胜负判定是在问一个图性质:连通、覆盖,还是奇点计数。本节把站内四个棋盘游戏拆开看,它们的源码里没有一行「棋」,全是邻接表、泛洪填充和可达集;反过来,读懂了这层图论骨架,规则说明书就可以扔了。动手版本:围棋、六边棋、一笔画挑战、跳棋。

棋盘即图

图 G=(V,E)G = (V, E) 的两个零件,棋盘天然齐备。顶点是棋子可以占据的位置,边是「一步可达」的关系。剩下的问题只是几何。不同的棋盘,本质是不同的邻接结构。

围棋和六边棋的站内实现都把棋盘摊平成一维数组,索引由 i=r⋅9+ci = r \cdot 9 + c 算出,邻居用同一个模式的函数枚举:

function neighbors(i) {
const [r, c] = rc(i);
const out = [];
if (r > 0) out.push(idx(r - 1, c)); // 上
if (r < N - 1) out.push(idx(r + 1, c)); // 下
if (c > 0) out.push(idx(r, c - 1)); // 左
if (c < N - 1) out.push(idx(r, c + 1)); // 右
return out;
}

四个边界判断,就是一个九路方格点阵的全部拓扑。

跳棋要复杂一档。它的孔位铺在三角形格子上,每个内点有六个邻居,直角坐标不好使,源码换用立方坐标。每孔记三元组 (a,b,c)(a, b, c),约束 a+b+c=0a + b + c = 0,六个移动方向写成 [1,0],[−1,0],[0,1],[0,−1],[1,−1],[−1,1][1,0], [-1,0], [0,1], [0,-1], [1,-1], [-1,1]。

哪些三元组算合法孔位?两条规则。三元组的每个分量绝对值都不超过 4,是中央六边形。某分量落在 5 到 8 之间且另两个分量在 [−4,0][-4, 0] 区间,是六条尖臂之一。

两条规则拼出 121 个孔的标准星形棋盘。中央六边形 61 孔,六臂各 10 孔。

一笔画挑战则干脆把「棋盘」这层皮也撕了。没有格点、没有棋子,一张 SVG 画布上直接画顶点和边。玩家面对的就是图本身。这大概是四个游戏里最诚实的一个。

还有一层容易被忽略的对应:棋盘状态按局部规则演化,一处的改变只影响邻接处。这个特征让棋盘和元胞自动机是近亲。

站内元胞自动机那篇讨论的生命游戏与野火模型,用的正是同一套「格点加邻居」的骨架。区别只在:元胞自动机没有对手,棋盘上有。

围棋的气

围棋规则里最核心的概念是「气」。一块棋的气,是与它正交相邻的空点。

这句话翻译成图论,是一个两步复合操作。先求同色连通分量,再数它的边界空点。

源码里的 group 函数把两步捏在一起。从一颗子出发,用栈式深度优先沿同色扩散,同时收集两样东西。连通块的所有棋子 cells,以及扩散途中撞见的所有空点 libs:

function group(start, b) {
const seen = new Set(), libs = new Set(), cells = [];
const stack = [start];
while (stack.length) {
const i = stack.pop();
if (seen.has(i)) continue;
seen.add(i); cells.push(i);
for (const n of neighbors(i)) {
if (b[n] === "") libs.add(n); // 空点 → 气
else if (b[n] === color && !seen.has(n)) stack.push(n); // 同色 → 继续扩散
}
}
return { cells, libs };
}

这就是泛洪填充(flood fill)。每次落子,程序对落点四邻的每个对方棋块跑一遍 group,谁的气数为零,谁整块从盘上消失。

随后程序再对己方新块跑一遍。如果自己也没气、且这手没提掉任何子,就是自杀,判非法。

一口气都不留的棋块在图上是一个「被完全割开的连通分量」。它的死亡不需要任何特殊代码,只是 libs.size === 0 这个布尔判断。

打劫

打劫是围棋规则里最绕的部分,实现却意外地朴素。

源码记住了上一手之前的局面(把整个棋盘数组 join 成字符串当哈希),每次落子前先算候选新局面的哈希。如果新局面与上一手之前完全相同,这手立即被拒。

劫争中对方提回一子会精确复原局面,于是被这道哈希闸门挡住。

代码里另有一个单劫禁着点的标记。当一手棋恰好提掉一子、且己方新块只此一子时,把相关点标记为禁着。

两层防线叠在一起,全局同形禁止的完整版(superko)被简化成了「不许立即重现上一手之前的局面」。对九路小盘,这个简化够用。

气的语义还藏着一笔账。

一颗孤立的子有四口气。两颗相邻的同色子拼成一块,八条边界里有一条变成了内部连接,气从八缩到六。

一般地,nn 颗子若连成树状的块,内部同色相邻恰有 n−1n - 1 对,每对吃掉两口潜在的气,气的上界是 4n−2(n−1)=2n+24n - 2(n-1) = 2n + 2。而棋子凝成实心大块时,气按周长增长、只与 n\sqrt{n} 同阶。

连接牺牲气,换取厚度。这块棋更难被分断,但呼吸的余量相对更小。

围棋里「宁失数子,不失一先」之外的另一条基本权衡,用一道小学算术就看得见。

数子

双方各停一手,棋局结束,接下来是数子。

源码的计分公式是:提子数 + 盘面存活子数 + 单色围住的空点数。

第三项又是一次泛洪。对每个尚未归属的空点区域做填充,看区域的边界接触了哪些颜色。只接触黑色,整片记黑方围空。黑白都接触,是公气(单官),不计。

两个泛洪(判死活、算围空)共享同一个 neighbors 函数。围棋的全部判定逻辑就这么大。

九路小盘上这一切肉眼可验证。一块棋的死活,就是它的空点集合会不会被清零。「封锁」一个子,就是用己方棋子把它的边界空点一个个占掉。这是图上的割,不是玄学。

顺带一提站内围棋路由里并排的两种节奏。标准模式不设时限。快棋模式给每一手 30 秒倒计时,超时的一方直接判负。同一个图引擎,套上不同的时间约束,就是两个玩法。

六边棋

六边棋(Hex)的规则一句话说完:红方要把自己两边的棋子连成一条带,蓝方亦然,先连通者胜。

站内实现是 9×99 \times 9 的点阵,每行比上一行向右错开 16 像素,画出菱形的斜切感。连通判定和围棋共享同一招。从己方已经贴上起始边的每颗子做 BFS 泛洪,看能不能漫到对边。胜负就是一次连通性查询。

但 Hex 在棋类里有个数学上罕见的名分:它是极少数被证明「先手必胜」而又「没人知道怎么必胜」的游戏。

论证分两步。

第一步,存在性。

Hex 有限、完全信息、(在经典六边形棋盘上)无平局。Gale 有个漂亮的定理:填满的六边形棋盘上,恰好有一方连通,不可能双方都不连通也不可能都连通。

定理的证明值得用一句话描个轮廓。把填满的棋盘当成红蓝两色的地图,沿着两色的分界线从头走到尾。这条分界线是一条一维的路径,它从棋盘的一个角出发,只能终止在两个特定的角之一。终点落在哪,就唯一决定了哪一方的两边被连通。

红蓝之间那条蜿蜒的边境线,本身就是「恰有一方获胜」的构造性证据。

三个条件凑齐,策梅洛定理保证双方之中必有一方存在必胜策略。

第二步,归谬,这就是著名的策略偷窃(strategy stealing),归功于纳什。

假设后手有必胜策略 SS。先手在棋盘任意位置落一子,然后宣布:「从现在起我就是后手」,按 SS 行棋。

麻烦只有一个。SS 某一步要求落的格子,被先手那颗「多余」的棋子占了。解决办法:那就随手另落一处。

论证的引擎是 Hex 的单调性:多一颗己子永远不会有害。棋子从不挡自己的路。你的连通带需要的是己方棋子的存在,而不是对方棋子的不存在。多出来的一颗子,最坏情况是闲置,任何情况下都不会把必胜局面变成必败局面。

于是先手在「多一子」的加持下执行一个必胜策略,必胜。与「后手有必胜策略」的假设矛盾。

这个论证的锋利和它的无用是同一件事。它证明了必胜策略存在,却一个子都不告诉你该下哪。

九路盘的局面数上界 381≈4×10383^{81} \approx 4 \times 10^{38},而一般化的 Hex 判定任意局面胜负是 PSPACE 完全问题。「先手必胜」与「无法计算必胜着法」在这款游戏上达成了长久的对峙。

棋盘上的拓扑告诉你赢的一方一定存在,却不肯透露是谁、怎么赢。

站内这个实现的 AI 是三层贪心,恰好是这个「不可计算」的日常注脚。

先扫描全盘空点,自己一手能连通就直接落(试探性地放子、判连通、撤回)。否则堵住对方的一手致胜点。都不成立,就按启发式打分。

每个空点数四邻,邻接己子加 2 分、邻接对方子加 1 分,再加一项中心偏好 N−1−∣r−c∣N - 1 - |r - c|,越靠主对角线的格子分越高。

会赢就赢、会挡就挡、其余凭感觉。一个诚实的三段式。

路由里另备双人模式,同一张棋盘换个人坐对面。无论对手是程序还是人,那个回合之间约两百毫秒的停顿,就是一台浏览器在它力所能及的范围内,对 4×10384 \times 10^{38} 种局面表示敬意的方式。

还有一个实现层的细节值得多看一眼。

经典 Hex 建在六边形格子上,六个方向互通。而站内为了让棋盘装进方格坐标,用的是四方向邻接,且双方共用同一套邻接。

这一改动有后果。方格加四邻接的世界里,双方交错填满棋盘时可以谁都连不通,经典定理不再兜底。

源码诚实地保留了这条退路:棋盘填满而无人连通,明确判平局。

「无平局」不是游戏名的属性,是邻接结构的属性。也就是把棋盘当图读才能看见的细节。

一笔画挑战

1736 年,欧拉解决哥尼斯堡七桥问题,论文里诞生了两样东西:图论,以及「一笔画」的判定法则。法则到今天也没变过:连通图里奇度顶点(度数为奇数的点)为 0 个,存在欧拉回路;恰 2 个,存在欧拉通路,且必须从一个奇点出发、到另一个收笔;其余情况不存在。为什么恰好是 0 和 2,下一节完整讲透,这里先看它怎么变成游戏机制。

一笔画挑战把这个判定做成了游戏的一部分。

挑战模式每题随机生成 9 到 11 个节点的图。先连一条打乱顺序的哈密顿路径,保证连通,也给每个点垫上基础度数。再撒 n+2n+2 到 n+5n+5 条随机弦把图加稠。

关键的一步在后面。程序有大约七成的概率把当前的奇度顶点两两配对、各补一条边,把图的奇点数强行修成 0 或 2。这道工序保证了多数题有解。剩下三成原样交卷,让「不存在欧拉通路」成为真实的辨认题,而不是失败惩罚。

出题器的两条分支,恰好对应玩家要练的两种能力:画出来,以及认出画不出来。

界面上那枚徽章直接把判定结果亮出来。「欧拉回路:任意节点均可起点」「欧拉通路:须从 a 出发,到 b 结束」(连必须的起终点编号都标好),或者「不存在欧拉通路」。

判定本身便宜得惊人:数一遍度数,O(E)O(E)。

但判定存在和画出解是两回事。知道从哪个奇点出发,不等于知道每一步该往哪走。

构造欧拉回路的 Hierholzer 算法能做到线性时间,Fleury 算法「每次不走桥」的谨慎策略也能逐边构造,只是桥的检测又是一项图论工作。工具把判定给你,把画留给你的手。

卡死的那条提示「还有边未走」,也值得翻译成图论。

程序判定卡死的标准很直白:当前节点的所有边都已用尽,而全图还有剩余的边。

这在欧拉定理的语言里只有两种可能。要么你提前钻进了本该最后收笔的顶点,把自己封死在里面。要么你已经与剩余的边失连,站在了一座孤岛上。

Fleury 那句「别走桥」的忠告防的正是后一种。桥是图上唯一的退路,烧掉它,剩下没画完的部分就被永远留在了对岸。

一笔画游戏的全部挫败感,就是不断用平凡的走法走进这两种死角。而逃脱办法只有一条:在奇点起笔,并且每一手都为尚未走过的边保留退路。

点相邻节点走边、走过的边变绿、卡死提示「还有边未走」、已解与尝试的计数存在 localStorage 里。这一套交互之下,是近三百年前就被完全解决的数学,和一个至今让人手滑的谜题。

跳棋

跳棋的路由里并排住着三种棋盘。

标准模式是上面说过的 121 孔星形盘,红蓝各十枚棋子分驻两条相对的尖臂。迷你星是同构的缩小版,每方六枚。西式跳棋(Halma)回到 8×88 \times 8 方格,两角的营地各占六格(r+c≤2r + c \le 2 及其对面),八方向行动。

三种棋盘共享同一套走子引擎,而这套引擎是一个精确的图算法。

走子规则:一枚棋子可以单步挪到相邻空位。或者跳,越过相邻的一枚棋子(自己的或对方的都行)落在它正后方的空位。跳完之后,落点自动成为新的起跳点,可以继续跳。

三种棋盘的差异只在邻接的密度。星形盘上每孔六向连通,方格盘上的 Halma 把四正方向加上四条对角线,单步与跳跃都是八向。对角线让跳跃网密了一倍,一回合的理论跨度也随之拉长。

源码用一次 BFS 把「本回合能到哪」整体算出来:

function gReach(src) {
const set = new Set(), q = [];
for (const t of step[src]) // 单步:相邻空位
if (t >= 0 && occ[t] === null) set.add(t);
const hops = (from) => step[from].forEach((land, d) => {
const mid = step[from][d];
if (land >= 0 && occ[mid] !== null && occ[land] === null && !set.has(land)) {
set.add(land); q.push(land); // 跳:有子可越、落点为空
}
});
hops(src);
while (q.length) hops(q.shift()!); // 连跳:落点作为新起跳点继续 BFS
set.delete(src);
return set;
}

(标准星形盘上的版本只把六个方向换成三角格的六邻接,逻辑一字不差。)

注意 BFS 队列里进出的只有跳跃落点。单步落点进了集合但从不入队,这意味着一回合是「一步」或「一串纯跳跃」,不能单步接跳跃,也就是正规规则的规定。

图论读法随之浮现。把每个空孔位看成辅助图的顶点,「越过某枚棋子跳得过去」看成边,一个回合就是在这张辅助图上走一条任意长的路径。

棋子在主图上是障碍,在辅助图上却是踏板。己方的子搭桥,对方的子也能借力。跳棋全部的互动性都藏在这条「万物皆可为跳板」的边上。

胜利条件是纯粹的图命题:把己方全部棋子(十枚或六枚)搬进对面的臂或营地。主图上的一次整体平移。

而 AI 的评估函数是一张距离表。

标准模式用立方坐标算六边形距离 d=(∣Δa∣+∣Δb∣+∣Δc∣)/2d = (|\Delta a| + |\Delta b| + |\Delta c|) / 2。这是三角格上两点间距的精确公式,立方坐标下三个分量的差各取绝对值再除以二,恰好把坐标系的冗余除掉。

枚举己方每颗棋子的每个可达落点,选让距离下降最多的一手。蓝方的目标钉在对面臂的尖角 (8,−4,−4)(8, -4, -4)。

迷你星和西式跳棋用目标营地质心的欧氏距离。出营重罚(加 500)、入营奖励(减 40)、微小随机抖动打破平局。

这是棋类 AI 里最简陋的一档。没有搜索、没有对手模型、没有开局库。但连跳的 BFS 可达闭包让它意外不弱。一次枚举就「看见」了所有链式跳的终点,贪心照着最远的那跳,招法看起来竟然像有意为之。

同一台引擎

回头看,四个游戏的判定各是一条图性质:围棋的死活与围空、六边棋的胜负是泛洪填充,跳棋的一回合是可达性闭包,一笔画的全部命运写在度数的奇偶里。棋盘给了棋类规则一个统一的编译目标:所有棋,先编译成图,再执行。

本文里出现的 AI 全都只看一步。把它们换成会展开博弈树的搜索(minimax、α-β 剪枝、蒙特卡洛树搜索,以及各自适配什么局面)是博弈与 AI 对手那篇的主题。那里的问题不是「图是什么样」,而是「沿着博弈树往下看几层才划算」。两篇文章合起来,正好是棋类程序的两层解剖:拓扑层负责规则与判定,搜索层负责决策。

这四个判定还有一个共同的气质:规则是局部的,判决是全局的。围棋的气只看相邻的点,死活却是整块棋的属性。一笔画的度数逐点可数,能否一笔画却是全图的属性。跳棋的跳跃只发生在相邻三格之间,一回合的可达集却能横跨棋盘。局部规则与全局性质之间的这道鸿沟,正是泛洪填充、BFS 这类算法存在的理由——它们是把局部信息层层外推成全局判决的桥。棋类规则写下来只需要一页纸,玩起来却深不见底,原因就藏在这道鸿沟里。

一笔画在这四个游戏里最特殊:它的判定定理三百年前就已完备,还长出了数回与数桥两个流行谜题。下一节把这条定理讲透。

欧拉路:从七桥问题到数回与数桥

1736 年,欧拉处理了一桩看似无聊的市政治安:哥尼斯堡有四块陆地、七座桥,市民想知道能不能不重复地走遍每座桥散步一圈。欧拉的回答否定了这个散步路线,但顺手发明了图论。把陆地缩成点、桥画成线,问题就从「地理题」变成了「图上的一笔画判定」。近三百年后,这个判定仍然活在两类流行的纸笔谜题里:数回(Slitherlink)让你亲手画一条闭合的一笔画回路,数桥(Hashi)让你按度数把一座群岛重新连成一张图。

七桥问题

先把结论完整地摆出来。

一个图里,经过每条边恰好一次的路径叫欧拉路(不回到起点则叫欧拉路,回到起点叫欧拉回路)。

判定只需要看两样东西。

连通性。除孤立点外,所有边连成一个整体。图都不连通,一笔当然画不完。

奇点个数。度数为奇数的顶点,必须有 0 个或 2 个。0 个奇点等价于存在欧拉回路。恰 2 个奇点等价于存在欧拉路(且必从其中一个奇点出发、在另一个结束)。奇点超过 2 个则无解。

两条判据合起来就是充要条件:连通 + 奇点数不超过 2。

这个条件的分量在于「必要」与「充分」两头都硬。不满足就一定画不成(没有侥幸),满足了就一定画得成(不靠灵感)。它把一个看似依赖巧思的操作题,压缩成两次计数。

必要性的直觉一句话就能说清:一笔画每经过一个中间顶点,都是「进来一次、出去一次」,用掉两度。

所以除起点和终点外,每个顶点的度数必须成双。起点和终点若是同一个(回路),全体顶点的度数都必须是偶数。

哥尼斯堡的四块陆地对应的顶点度数是 3、3、3、5。四个奇点,散步路线数学上不存在,与走法多聪明无关。

顺带补一个能让判定更快的小定理:任何图的奇点个数必为偶数。

这就是握手定理。所有顶点度数之和等于边数的两倍(每条边恰好贡献两端各一度),总和是偶数,奇数个奇点的度数和就落不进偶数里。

这条「奇点成对出现」的守恒律在判定时能省事。数出一个奇点就必还有第二个,数出三个就必还有第四个。

它也是很多一笔画智力题的秒杀器。「这个图形能不能一笔画」先扫奇点,三个以上直接判死刑,连图都不用细看。

哥尼斯堡的那张抽象图还有个常被误解的细节值得澄清。欧拉 1736 年的原始论文里其实没有画那张著名的点线图。他用的是字母和表格来罗列陆地与桥的组合关系,今天的「欧拉图」插图是后人按他的思想补画的。

这个细节反而凸显了那一步抽象的分量。连「画成图」这个动作都是后人觉得顺手才补上的,真正被发明的是「把地理问题翻译成组合关系」的思维方式。

谜题玩家每天在点阵与岛群上做的事,本质是站在这次翻译的下游。

充分性不那么显然(「奇点不超 2 就真的一笔画得完吗」),证明靠算法给出。这比任何构造性论述都有说服力。

Hierholzer 算法

判定了存在,还要能走出来。

Hierholzer 算法的核心观察是:把几个圈在公共顶点处拼接,还是一个一笔画。

流程:

function euler(起点):
栈 = [起点], 路径 = []
while 栈非空:
v = 栈顶
if v 还有未走的边 (v, u):
划掉这条边, 栈.push(u)
else:
路径.push(栈.pop()) # v 已无路可走
路径.reverse() 即一条欧拉路

它先把当前位置能走的边贪心地走到黑,走投无路就退栈记一笔。退栈记下的碎片恰好是一个个圈,反转拼接后就是完整答案。

每条边只进栈出栈各一次,复杂度 O(E)O(E)。存在性判定(数奇点)加构造(Hierholzer),一笔画问题从判定到求解都被压到了线性时间。

奇点超标怎么办

现实世界不会因为你奇点超标就取消扫雪任务,于是判定问题升级成优化问题。

允许重复走边,最小化总重复里程。这就是中国邮递员问题(1962 年由管梅谷提出,故得此名)。

它的解法漂亮地接回了欧拉。重复走边等效于给图添边,添边的目标是让所有奇点度数变偶。每个奇点恰好需要再补奇数度,最小方案是把奇点两两配对,按点对间的最短距离配对连边,在一般的稠密情形归结为最小权完美匹配(多项式可解)。

匹配完所有顶点度数全偶,欧拉回路存在,Hierholzer 上场走完全图。

于是「扫雪车路线」的完整配方是:找奇点、配对加边、一笔画。

欧拉判定负责「能不能」,邮递员负责「不够格就花最少的钱补到能」。一个判定定理与一个优化算法首尾相接,是图论里少见的干净接力。

这个「判定便宜、构造也便宜」的组合在现实里到处接活。道路除雪与垃圾车路线希望不重复地扫完每条街,电路板钻头希望空行程最短,DNA 测序的片段拼接被建模成读串重叠图上的欧拉路。都是同一个 1736 年的答案在换行业上岗。

DNA 那个应用值得展开半句。测序仪吐出的成千上万段短读串,拼接成完整基因组的任务,在重叠图视角下就是「找一条经过每条边一次的路径」。现代测序算法绕开哈密顿路径(NP 难)改走欧拉路(线性时间),正是这一步「换问题」让全基因组测序从学术豪赌变成了常规操作。

一个三百年前的散步问题,今天在测序仪里每分钟被解开成千上万次。

数回

数回的棋盘是点阵,格子里的数字 0 到 3 表示该格四条边中被回路占用的边数。任务是沿点阵的边画出一条闭合回路。

欧拉在此处不是类比,而是直接的规则内核:回路天然是欧拉回路。它经过每条用到的边恰好一次,且每个被触及的点恰好进一次出一次。

画回路时必须遵守的一笔画面约束「每个交点处的线头只能是 0 根或 2 根」,不需要你额外操心,它是「回路」这个定义自带的。

谜题设计者的聪明在于把判定问题反过来用。一笔画判定告诉你「什么样的图形画得成」,数回让你在无数画得成的图形里,找出同时满足数字提示的那一个。

也因为这个内核,数回有一个一票否决的自检标准:画到一半出现「线头」(度数为 1 的交点)或提前合圈(没吃完提示就闭路),当前推理必有一错。别的谜题错了要等检查,数回的错误当场就能被几何形状出卖。

于是解题的全部推理都发生在「数字提示 + 度数约束」的合取上。几个最锋利的传播规则。

0 的封杀。数字 0 的格子四条边全部为空。它是最强的一条单元传播,也常是整盘的破局点。

3 贴 0。一个 3 与一个 0 共享一条边。共享边因 0 而必空,3 的「恰好空一条」额度被用掉,其余三条边全部必实。两条规则咬合,一次钉死四条边。

角落的 3。贴着棋盘角的 3,两条靠边的边必实。若其中一条为空,角顶点只剩一条实边,度数为 1,违反回路约束。

这个推理值得细品。它没有用到任何数字信息之外的规则,纯粹是「每个顶点度数为 0 或 2」在几何角落处的特化。

顶点度数作全局传播器。任何时刻某个顶点已有两条实边,其余关联边立刻全空。已有两条空边之外再压一条空边,剩余实边立刻全实。

这类「度数封顶/封底」的检查在每一步之后都要全盘重扫。人工玩家是靠眼睛扫,求解器是靠循环扫,规则相同。

定式之间的配合也有讲究。

3 的威力不止于自己那一格。一个 3 的两条相邻外边被别的定式压掉一条,其余三条立刻全实。数字给的是「恰好三条」的等式约束,任何一方的增减都会触发全格重解。

熟练玩家看数回盘面的顺序因此有迹可循。先扫 0(免费封杀)、再扫角落与边缘的 3(几何位置让等式自动收紧)、最后才碰 1 和 2(约束最松,通常是等前面的推理来喂)。

约束的松紧排序决定推理的扫描顺序。这条经验对所有约束类谜题都成立,数回只是把它表演得最直白。

比这些定式更深一层的武器是奇偶性。

回路把平面分成内外两侧,从盘面外走到盘面外必须穿越回路偶数次。

于是一条「假设边」到底实不实,可以用「穿过这条边从外侧出发绕一圈回来穿了几次」来检验。这就是人的「内外直觉」的严格版本。

数回因此有个迷人的双重身份:它是欧拉回路的存在性游戏,也是平面图对偶性的手感训练场。

定式卡住的时候,人类玩家还有一手「假设法」,而它就是深度优先搜索的手动档。

挑一条歧路边假设为实,把后续推理(传播)全推一遍。推出矛盾就翻面(该边为空),推出全盘完成就收工。

这与求解器的「传播 + 回溯」逐字对应,区别只是人要在脑子里维护一个撤销栈。

数回是少数假设法能被严格检验的谜题。回路约束的「每点度数 0 或 2」足够锋利,假设的错误通常在几步之内引爆一个度数为 1 或 3 的顶点,而不像某些谜题要推到最后才见分晓。错误反馈快,是它对推理玩家的额外友好。

顺带一提生成端。本站数回支持不同尺寸并带检查功能,盘面生成走的仍是「先造解、后验证唯一解」的老路。

先画一条合法回路,再从提示的逆运算反推数字,逐格验证谜面唯一可解。

生成器还会面对一个数回特有的选择:提示给多密。

同一个解,每个格子都标数字则推理平凡无趣。挖掉提示则难度上升,但挖得太狠会出现「线索组合不出唯一解」的死局,只能回退重挖。

这和四个谜题里的数学里扫雷「无猜盘」的生成哲学同源:谜题的品质上限不在解法端,在生成端的唯一性保证上。

数桥

数桥的规则。盘面上散布着标有数字 1 到 8 的「岛」,任务用双桥、三桥(水平或垂直的直线,至多三条并排)把岛两两连接。

要求每个岛的桥数恰好等于它的数字。桥不得交叉也不得压过其他岛。完工后所有岛连成一个连通图。

注意数桥与欧拉路的方向恰好相反。欧拉路是「图已给好,找出那条边序列」。数桥是「度数已给好,把图重新构造出来」。

但欧拉留下的核心工具原封不动:度数。

岛上那个数字在图论语言里就是顶点的目标度数,解题的全部推理围绕「每个岛还欠几度、哪些邻居还能还债」展开。

几条高频定式,全部可以用度数 + 连通性两条原则推出来。

8 必满、6 在边必满、4 在角必满。

岛的度数来自四个正交方向,规则限定每方向至多两座桥。所以数字 8 的岛四面各拉两座桥,直接满上。数字 6 的岛若贴边(只有三个可用方向),三方向各两座,同样满上。数字 4 的岛若在角落(只剩两个方向),两方向各两座。

位置限制了可用方向的数目,数字给出了总额,缺一个都凑不齐的就必须全额执行。

1 不连 1。

两个度为 1 的岛若相连,它们组成的连通分量就与世隔绝了(各自的唯一额度用尽),除非盘面上只有这两座岛,否则直接违反全图连通。

这条「负面规则」不告诉你要做什么,只告诉你什么不许做。剪枝的一等公民。

3-1 对角定式。

一个 3 与一个 1 处于对角位置,则 3 背对 1 的那两个方向至少各有一座桥。

直觉证明:若 3 在那两个方向都空着,它的桥只能全挤在朝向 1 的两个方向上,而 1 的唯一一座桥无论朝哪连,都会造成某个位置无可避免的交叉或封锁。

数桥老玩家背它当口诀,本质上是一条「度数预算 + 平面不交叉」的联合传播。

数桥里最深的推理还来自连通性的主动运用。

任意时刻把「已连成一片的岛群」缩成超点,考虑「这个超点还剩哪些出口」。

若一个岛群只剩一个对外桥位,那这个桥位非用不可。若某组岛的剩余桥位把盘面劈成两半而任何一半里还有别的岛,某些连法立刻排除。

这些是图论里的割集思维,程序实现里通常请出并查集。每落一座桥就把两端的岛合并进同一集合,任何时刻「是否全连通」是一次查询,「某个桥位不能落」可以是「落了之后另一半岛群将永远孤立」的反向判定。

本站数桥的检查功能负责兜底验证,而推理的快乐全在落桥之前。

数桥还有一个与数回对称的「负推理」习惯值得专门说:排除法在这里不是辅助,而是主力。

1 不连 1 的规则每排除一条候选桥,相应岛的「可用方向」就少一个,度数配额随之收紧。配上「某方向已满」的正面结论,两股力量在盘面上反复挤压,大多数盘面不需要任何假设就能滚完。

这种「正面配额 + 负面排除」的双向挤压,是所有「按度数造图」类推理(数桥、度数视角的 Slitherlink 顶点分析、甚至电路网表分析)共同的推理语法。

一个词汇上的巧合也值得玩味。

图论里「删掉后图不再连通的边」叫割边,也叫桥。与数桥里那些横跨水面的桥同名而不同物。

但在解题的最高层,两个「桥」碰头了。提前落成的桥结构可能构成图意义上的割边。它一旦存在,两端岛群的对内配额与对外出口就被它独占锁定,后续所有推理都要在「这个连通方案不可撤销」的前提下进行。

谜题的名字来自表象,推理的深处撞上的是同一个连通性概念。这种「名字是巧合、数学是必然」的时刻,是图论谜题独有的浪漫。

把两个谜题并排放回欧拉的框架里,对照就清楚了。数回是在一张固定的网格图上选边,选出的子图要求全偶度数且连通(回路)。数桥是在一张隐形的完全图上选边,选出的子图要求度数逐点对齐且连通(生成子图)。一个的指纹是「偶」,一个的指纹是「配额」。但两者的解题体验惊人地同构:都是从「必然结论」(传播)开始,推不动了才假设(回溯),而且都把最深的乐趣藏在连通性这个最朴素的词上。数回玩家常用的「跟着墙走」口诀(已定的实边连成的小段必须最终闭合成圈,任何一段的两端都被堵死就是矛盾)与数桥玩家常用的「配额挤兑」,其实是同一条传播原理的两种方言。

度数是图给的硬约束,但逻辑谜题家族里还有一批长相更悬殊的成员,它们共享的结构更深:每个格子只有两种归宿,规则全是「格子之间必须满足的关系」。这正是约束满足问题的标准长相。

一个求解器统一四类逻辑谜题

纸笔谜题家族里有一批长相悬殊的成员。数织用行列数字提示你涂格子,Hitori 要你划掉重复数字,Akari 让你在房间里放灯照亮每个角落,数墙(Nurikabe)则要求黑格子连成一片又不许挤成方块。

规则一眼看去毫无亲缘,但它们共享一个深层结构:每个格子只有两种归宿,规则全是「格子之间必须满足的关系」。也就是约束满足问题(CSP)的标准长相。

统一建模之后,四个谜题可以被同一个求解器通吃。而这个求解器的骨架只有两招:传播(把确定的结论推到底)与回溯(推不动了再猜)。

本站四块棋盘正好各选一个:数织、Hitori、Akari、数墙。数独是这个家族里最有名的成员,建模思路一脉相承,机器怎么出题的部分已在数独的两面里讲过,这里专注「四合一」的建模与求解。

CSP

形式化只需要三个词。

变量:每个格子是一个变量。

域:变量能取的值。纸笔谜题里几乎清一色是 {黑, 白}(或等价地 {灯, 空}、{划掉, 保留})的二元域。

约束:规则翻译成「哪些变量取值组合是被禁止的」。

一个解就是给所有变量赋值且不违反任何约束。谜题设计的核心承诺是解恰好存在一个。唯一解让「推理」成为可能,玩家推导的每一步都有唯一正确的目的地。

这个视角立刻带来一个好处:「会玩」和「会解」从此分家。

人玩谜题靠的定式与直觉,是约束传播的人类友好版本。机器解谜靠的是同一套传播加上不知疲倦的回溯。

四类谜题的规则千差万别,翻译成 CSP 后区别只剩「约束长什么样」。而这恰恰是框架的价值:换谜题不用换求解器,只换约束的编码。

四类谜题的建模

数织

数织的变量是每个格子的黑白,约束却不是「相邻关系」而是整行/整列的段结构。

一行左侧的提示 [2, 1, 3],意思是这行从左到右依次出现长度为 2、1、3 的三段连续黑格,段间至少隔一个白格。

这类「排列型」约束没法拆成逐对格子的简单规则,标准处理法是行内枚举。给定提示 [2, 1, 3] 和行宽,所有满足段结构的黑白排布可以全部列出来(或用 DP 计数)。

对某一行,把「当前已确定的格子」与每个合法排布比对。凡是所有合法排布里都是黑的格子,必然黑;都是白的,必然白。

一行十几个格子的谜题,合法排布往往只有几个,一行扫过去就能钉死好几个格子。

数织因此是「传播力」最强的谜题之一。几乎每条新信息都能让某些行的合法排布集收缩,收缩又反过来影响交叉的列。

行内枚举本身也可以不用蛮力。把提示序列看成游程编码,「生成第 k 个游程从位置 i 开始」构成递归结构。fits(i, k) 表示「从第 i 格起放下第 k 到最后一段的所有方式」。

对每个可行的起点递归下去,用一张二维表缓存(位置 × 段序号)就能把指数级的排列数压成多项式。动态规划最朴素的样子:状态的两条轴是「格子走到哪」和「段用到哪」。

段提示还能给出一些「无关于搜索」的硬信息。一段 8 在宽 10 的行里无论放哪都必然盖住第 3 到第 8 格。「长游程的重叠区」是人肉玩家最爱的一口价推理,实现上等价于对所有合法排布求位与。

Hitori

Hitori 的盘面填满数字,玩家把部分格子涂黑,规则三条。

每行每列剩余的白格里数字不得重复。黑格不得正交相邻。所有白格必须连通。

建模后它是三类约束的缠结。

不重复是「逐对格子」约束(同行同列的同号白格对,至少黑一个)。黑不相邻是另一批逐对约束(相邻格至多黑一个)。白连通则是最讨厌的全局约束:它不归属任何一对格子,只能整盘检查。

Hitori 的解题定式几乎全从前两族推出。同一个数字若被「第三个同号」夹住,中间那个必白。十字邻位的同号格,中心必黑。

第三族的连通性通常只在残局兜底。这个结构上的主次之分,直接对应人类玩家「先推十字、再看孤岛」的操作顺序。

Akari

Akari(站内译名「点灯」,与 Lights Out 无关,后者是翻转开关的代数谜题)要求在白格里放灯。

每个白格被至少一盏灯照到(沿行列直线、遇黑格阻挡)。任何两盏灯不得互相照见。带数字的黑格要求正交相邻的灯数恰好等于数字。

CSP 视角下它有个漂亮的对偶结构:互斥约束说「每条无阻挡线段至多一盏灯」,覆盖约束说「每个白格所在的行段或列段至少一盏灯」。一个是不许多,一个是别少。

数字黑格则是精确计数约束。

三个方向上「放灯格」与「被照格」的传播规则都因此格外锋利。一个数字 0 的黑格,四邻全白保平安。一个尚未被照亮的白格,若唯一的照明来源是它自己,那它必是灯。

把「线段」抽出来看还能再进一步。每条水平或垂直的无阻挡白格段是一个原子单位,灯只能放在段上。而一个白格 = 恰好一条行段与一条列段的交点。

于是「照亮每个白格」等价于「每个交点被它的行段或列段覆盖」,「灯不相照」等价于「每段至多选一次」。这几乎是一个覆盖问题的教科书描述。

这类「谜题背后站着经典组合优化问题」的现象不是孤例。它解释了为什么纸笔谜题的推理总是严丝合缝:好的谜题规则往往就是某个经典数学结构的规则,只不过穿了件房间和灯泡的外衣。

数墙

数墙(Nurikabe)的规则。

每个数字声明一个由白格组成的「岛」的大小,岛内恰含一个数字、岛与岛不得正交相接。黑格全体必须连通。黑格不得形成任何 2×2 的方块。

它是四者里全局约束最重的:黑连通、岛隔离、岛大小、无 2×2,四族约束同时在线。

本站数墙用 5×5 的盘面把这套缠结装进一分钟就能上手的一局。小盘不是简化,而是让「岛的大小即约束」这个核心矛盾露得更清楚:一个数字 4 的岛,若已确认三个白格,第四个白格的位置就成了强传播点。

它的高频推理也值得列两条,因为都带着「两种可能性互斥」的味道。

一个数字 1 的孤岛,四邻必须全黑(岛不许扩张)。而它四邻的某个格子若同时是另一个岛的成员,两条规则当场打架,替你排除一种连通方案。

2×2 约束则是最冷酷的「局部否决」。任何三个黑格的 L 形,第四个格子被迫变白。哪怕那个格子还隶属于某座没凑满的岛,两规则的冲突会顺着传播链一路传导,逼更早的假设翻面。

局部否决 + 全局守恒的组合,正是数墙推理深度的来源。

四类建模摆在一起,共同点比差异更扎眼:变量全是 0/1,差别只在约束的形状。

数织的约束是排列型,Hitori 和 Akari 的主力是逐对型,数墙背着一堆全局型。这个差别将直接决定各自求解时的难度排序。

传播-回溯框架

统一建模后,求解器只有十几行:

function solve(赋值):
if 有变量违反约束: return 失败
if 全部赋值完成: return 成功(记录解)
传播到不动点:
repeat
for 每条约束: 用已知变量收缩其他变量的域
until 没有任何域再收缩
if 出现空域: return 失败
选一个未定变量 v(优先候选最少的)
for v 的每个候选值:
if solve(赋值 + {v: 值}) 成功: return 成功
return 失败

三个零件各司其职。

传播是「推理」的代名词。用部分赋值反复收缩未定变量的域,直到推不出任何新结论。上文里数织的「所有合法排布取交集」、Hitori 的十字定式、Akari 的「唯一光源必是灯」,都是特定约束上的传播规则。

传播是安全的:它只推出必然结论,绝不猜测。

回溯是推理耗尽后的兜底。挑一个变量试一个值,走进去继续传播,失败了就撤销重试。

变量排序(MRV,最少剩余值优先)是性价比最高的一处优化。先猜候选最少的变量,猜错的分支死得快,剪枝早。

实现层还有一个不起眼但决定性能的组件:传播队列。

每赋一个值,受影响的约束才需要重扫。把「哪些约束还可能推出新东西」放进队列,依次弹出处理、处理中产生的新收缩把新约束入队,直到队空即为不动点。

没有队列的做法是每轮无差别重扫全部约束,结论一样,代价是常数倍的浪费。

四类谜题里数织的约束最少(行加列共 2n 条)、数墙最多(连通与 2×2 检查全盘无数条),队列化的收益因此完全不同。同一套框架,不同谜题的性能画像由约束的数目与形状决定。

这套框架的分工还有一个值得单独说的性质:传播和回溯的成本结构完全不同。

传播是多项式的、结论是必然的。回溯是指数的、结论是「假设的」。

所以衡量一个谜题「是否适合人类」,一个被广泛使用的标准就是:纯传播(零回溯)能不能解。

能,人类玩起来步步有据。必须深回溯才能解的盘面,人玩就是连续瞎猜。生成的谜题若不检验这一点,「推理游戏」会退化成「运气游戏」。

传播的强度可以调档

「传播」内部还有档位之分,选择哪一档是个典型的工程权衡。

最低档叫前向检查。某个变量刚被赋值,只检查它直接参与的约束,把邻居域里立刻冲突的值删掉。便宜,但信息浪费一半(邻居的收缩不继续往下传)。

中档把传播循环跑到不动点。每次收缩都触发重扫,直到再也推不动。成本高一些,推出的结论多得多,纸笔谜题基本都配这一档。

最高档是完整的弧相容维护(如 AC-3 算法)。任意时刻任意约束的两端,任何一方的每个取值在对方那里都必须有「搭档」,没有就删。理论最干净,但维护队列的开销在 0/1 谜题这种小域场景里往往不值。

谜题求解器的常见答案是中档起步、按需升级:先跑便宜的不动点循环,回溯深了再考虑上更强的传播。传播强度是又一个可调的档位,调多大取决于回溯出现的频率。

传播到不动点

用 Hitori 的经典「三连」定式走一遍传播链,体会「两条规则咬合」是什么意思。

设某行连续三格都是数字 4:[4, 4, 4]。

不重复约束说这三个白格至多剩一个,即至少要黑两个。黑不相邻约束说黑格彼此不能挨着。三个连续格子里挑两个互不相邻的,唯一方案是黑两端、留中间。

两条规则各自只给了「至多/至少」的松话,交叉点上一击必杀:中间必白,两端必黑。

而且链条不停。中间那个 4 确定为白后,该行、该列上其余的所有 4 都因与它重复而必须黑掉。这些新黑格又带着「黑不相邻」去约束各自的邻居。

一次定式触发,约束像多米诺一样向外滚,直到某一轮传播再也推不出新结论。这就是「传播到不动点」的字面含义。

Akari 那边对应的例子是「唯一光源」。一个白格尚未被照亮,扫它所在的行段和列段,发现所有能放灯的位置里只有它自己还没被排除。那它必是灯。

数织的版本是行交集。某行所有合法排布里第三格全黑、某列所有合法排布里第三格全白,两信息一交叉,矛盾立刻逼出某一侧的重算。

三个谜题的推理表面上千姿百态,骨子里全是同一件事:用已知收缩未知,直到某个未知只剩一个候选。

回溯兜底的场景也演示一下。

Hitori 残局里两个格子归属未定(黑或白),传播不动了。求解器试探「黑」。三步之内某个区域的白格被黑格切断,连通约束失败,回滚。改试「白」。传播链一口气滚完全盘。

人类的复盘里叫「假设法」,程序里就是深度优先的两层递归。同一个动作,人做是聪明,机器做是遍历,区别只在机器从不忘记撤销。

唯一解与出题机器

求解器还有个逆向用途:出题。

流程是先生成一个满足全部规则的完整终局,再逐步「挖洞」。每遮住一部分,就调用求解器验证剩余盘面仍唯一可解。一旦出现多解就回退这一步。

这与数独出题是同一台机器。数独的两面里管这叫「解计数器」,逐洞验证唯一性。

四类谜题的生成器全走这条路。本站的数织和Hitori盘面随机生成后同样依赖这一步保证可玩性。数织还多给一个难度旋钮(黑格密度),密度越高行列约束越紧,求解越吃推理。

生成成本里最大的一块正是反复调用求解器:出题的难度是解出来的,不是设定的。

求解器还能再往前一步:量化难度本身。

让求解器带统计地跑一遍谜题。记录传播迭代了几轮、回溯了几次、假设链最深多少层。这些计数就是难度的客观读数,比「作者觉得难」可靠得多。

数独社区的「技巧分级」(这题需要唯一矩形、那题需要链)本质上是同一件事:把求解所需的推理强度当作难度的定义。

人工难度分级靠专家背定式表,机器分级靠求解器吐计数。两者一致时说明定式表确实覆盖了传播的全部形态。这反过来也是验证「人类解法完备性」的旁路。

难度从哪来

最后回答一个开头埋下的问题:为什么这四个谜题的「手感」难度排行大致是数织最顺、Hitori 居中、Akari 要多想一步、数墙最容易卡死?

答案就藏在约束形状里。

数织的段约束自带强传播(一条提示一行信息),且行列交叉形成天然的联动放大。

Hitori 的逐对约束传播规则多而确定,但全局连通性让残局暗藏返工。

Akari 的覆盖-互斥对偶要求同时盯住「少」和「多」两个方向。

数墙的全局约束(连通、无 2×2)传播力最弱。很多结论要等到盘面大半确定才浮现,传播不动点来得晚,回溯(假设法)出场率高。

约束越全局,传播越钝,人对推理的体验越「烧脑」。这条规律也反过来指导出题:想让谜题「人性化」,就多给逐对与排列型的约束、少依赖全局连通性。

回到求解器本身,还有两件低成本武器值得记录。

其一是上文的变量排序。这里把 fail-first 的原理补全:挑「剩余取值最少」的变量先猜,让分支因子最小的节点最先被处理,猜错时在搜索树浅层就暴露矛盾,砍掉的是整棵子树而不是几片叶子。

其二冲突学习。每次回溯记下导致失败的变量组合(nogood),再次遇到同构局面直接剪掉。相当于给搜索装上「吃过一次亏就不会再吃」的记忆。

两者都不改动建模本身,却常常把搜索量压掉一个数量级。代价是每次决策多花一点记账时间,谜题盘面小、约束简单时未必划算。纸笔谜题的规模通常到不了那个临界点,传播到不动点之后,剩余需要真猜的格子往往只剩个位数,朴素框架就是最划算的选择。

同一个求解器,四副面孔。

收尾前把视野再放宽一格:CSP 的变量不必是 0/1。

数独的变量域有九个值,它的「每行每列每宫不重复」是一条 all-different 约束。一组变量互相取值不同,可以被建模成二分图匹配问题,用匈牙利算法整块处理,比拆成一堆逐对「≠」约束传播得更快更远。

但纸笔谜题家族集体偏爱 0/1 域并非偶然:二元状态让「涂格子」这个物理动作成为约束的完美载体。人拿铅笔就能执行传播;九值域的系统没有对应的身体动作,只能出现在数独这种用数字本身当符号的特例里。

从数织到数墙,从数独到斑马谜题,约束满足是一个横跨所有「逻辑游戏」的统一底层。下一节的斑马谜题会把这个统一再往前推一步:线索从格子换成了句子,骨架一模一样。

演绎推理的三条路线

有一类游戏号称「不需要任何知识,只需要推理」。斑马谜题给你五栋房子十四条线索,珠机谜阵(Mastermind)给你十次猜测的黑白反馈,逻辑推理合集给你一串图形数字让你找规律。

它们的共同点是解题过程里没有一克「灵感」的合法含量。每一步要么是必然的推理,要么就还不该下结论。

但仔细拆开,三者的演绎结构其实完全不同。斑马谜题的线索一次性全部给齐,Mastermind 的「线索」要靠猜测逐条购买,逻辑合集的「规律」则根本不保证唯一。

先把「演绎」这个词的标准立起来,本节全篇用它当尺子。一段推理是演绎的,当且仅当结论已经藏在前提里,推理只是把它挖出来。换句话说,正确的推理不允许引入任何前提之外的新信息。

以此衡量,「猜」、「直觉」、「找感觉」都不合格。而合格的方式其实相当有限:把前提写成明确的约束,然后用两种动作(收缩、分情况)反复加工。

三类游戏对应这三种动作的三种配比,这就是本节的路线图。

爱因斯坦谜题

斑马谜题最流行的版本顶着「爱因斯坦出题、只有 2% 的人能解」的名头。本站爱因斯坦谜题用的正是这个经典盘面:一排五栋房子,每栋有颜色、国籍、饮料、香烟、宠物五个属性,五栋房子在每类上恰好取遍五种取值;十四条线索诸如「绿房子紧挨在白房子右边」「老金烟民养蜗牛」。

传说营销先放一边,事实是:线索给全、解唯一、每条线索都是严格命题,这个谜题不需要任何超越机械推理的步骤。

1970 年代起它就被反复用作教学例题,「2%」从来是宣传语,不是统计。

先做形式化。五个属性类别、每类五个取值,等价于一个 5×5 的拉丁方约束系统。把每个类别看成「取值到房号」的一个排列,谜题就是对五个排列同时求解。

十四条线索逐条翻译,全部落进四种类型。

肯定型:「红房子住英国人」。某属性取值与另一属性取值绑定。

否定型:「丹麦人不喝茶」。排除一个绑定。

相邻型:「养马的人住蓝房子隔壁」。两个取值的房号差的绝对值为 1。

顺序型:「绿房子紧挨在白房子右边」。房号比较,且比相邻型更紧(方向确定)。

四种类型对应四种约束算子,这就是全部词汇量。斑马谜题的「语言」小到可以一张表列完,也就是它适合机械化的原因。

人类解法

人类的「表格打叉法」值得被严肃对待,因为它就是约束传播的手工形态。

画一张 5×5×5 的表格(属性 × 属性 × 房号),每确定一个绑定就整行整列打叉(同一个取值不能出现在两栋房子,all-different 约束的人类版本),每排除一个绑定就打一格叉。

叉到某个格子的行只剩一个空位,那个绑定自动成立。这就是数独玩家说的「唯一候选」,两套谜题在底层推理上是同一套。

十四条线索在纸上轮番扫过,配合 all-different 的行收缩,绝大多数盘面在两三遍扫描内收敛到唯一解。

拿经典盘的开局走两步感受传播链。

两条锚点线索先落位:「住最左的是挪威人」、「住正中的人喝牛奶」。房号 1 与饮料「牛奶」当场钉死。

接着「挪威人住蓝房子隔壁」把 2 号房染成蓝色。再看「绿房子紧挨在白房子右边」配「绿房子主人喝咖啡」:绿白相邻的组合不能占 (1, 2) 位(2 号已是蓝色),绿房子也不能落 3 号(那里已钉了牛奶,而绿房子的主人喝咖啡)。候选瞬间收窄到 (3, 4) 与 (4, 5) 两组。

四条线索各说各话,叠起来已经把颜色与饮料的排布砍掉近半。每条线索单独看都是废话,组合起来却层层咬合,这种「组合出必然」正是斑马谜题的全部机关。

机器解法

机器端最直接的方案是按类别枚举排列。

对「颜色」类别试一个 5! 排列,再对「国籍」试一个。上界 (5!)5≈2.5×1010(5!)^5 \approx 2.5 \times 10^{10},全枚举不优雅,但剪枝后立刻塌缩。

关键剪枝有两个。每加一个类别立刻跑一遍所有已给线索(一个类别未定就不判相邻型,涉及它的线索先挂起)。前缀冲突即剪:顺序型与肯定型线索在类别的前缀枚举中可以直接砍掉成片的排列。

另一个反向思路是按房号递推。从 1 号房开始,每个房试一个「五元组」(颜色、国籍、饮料、烟、宠物的组合),相邻型线索在房与房之间即时检验。深度只有 5,每层候选被前面房号的排除大幅压缩。

工程上这个解法毫秒级出解。更讲究的做法是套用约束满足问题(CSP)的通用框架,变量是「每个取值的房号」,域是 1 到 5,线索做传播器,同样的十几毫秒。

人类与机器在此地撞车:两边都是传播加搜索,只是人类的「传播器」叫定式,机器的叫循环。

本站爱因斯坦谜题实现的是经典固定盘(喝水的是挪威人、养斑马的是日本人那一版),线上提供逐条提示与答案检查。它更像一个精心布置的推理健身房,而不是每次随机的考题。

固定盘有个常被低估的好处:解可以被人类口口相传。你能把自己钉死的推理链讲给别人听,这在随机生成的谜题里很少成立。

出题端顺带补一笔。如果要机器生成新的斑马谜题,流程仍是约束求解的老三样。先随机抽一个完整解(五个类别的排列各随机一份),再随机生成候选线索并逐条检验「全部线索交起来是否恰好唯一确定这个解」。多余的线索往下删,删到再删一条就出现多解为止。

线索的最小集就是难度的一个天然标度。同样唯一可解,十四步推理和二十步推理的盘面是两个难度档。

这套「解 → 线索 → 唯一性验证」的管线,与数独出题、扫雷无猜盘是同一台机器的不同机位。

Mastermind

斑马谜题的线索是天赋的,珠机谜阵的线索要靠猜。

规则:隐藏一个四位密码,六种颜色可选(可重复),你每猜一次得到反馈。位置与颜色都对几个(黑钉)、颜色对但位置不对几个(白钉)。

本站珠机谜阵按此实现:六色四位、最多十次机会、反馈逐钉显示,猜错还能逐步撤销复盘。

问题从「解一个给定约束系统」变成了「在预算内设计一个购买线索的序列」。

候选集过滤

演绎的骨架仍然是机械的:任何一次猜测的反馈都会砍掉候选密码集。

初始候选是 64=12966^4 = 1296 个。你猜 1122,得到「一黑一白」,就把所有「假设它是答案时反馈不是一黑一白」的候选全部剔除。这条一致性过滤每轮执行一次,候选集单调收缩。

只要候选集还有多个元素,就继续买信息。收到反馈后先跑过滤再决策。这是所有 Mastermind 求解器共用的底盘,区别只在下一次猜什么。

这套过滤还有个反直觉的性质值得单独看:猜测根本不需要可能是答案。

你可以故意猜一个明知不可能是密码的组合(比如四个重复色,而你已确信密码无重复),只要它的反馈能把候选集劈得足够开。它是「问题」而不是「答案」,问得刁钻比答得接近更有价值。

人类玩家几乎本能地抗拒这一点(总觉得该朝答案逼近),而最优策略常常在前两轮「横着劈一刀」,第三轮才开始认真逼近。

演绎游戏的直觉与最优策略之间的这道裂缝,是 Mastermind 教给你的最锋利的一课。

Knuth 的极小极大

1977 年 Donald Knuth 给出的策略至今是理论标杆:每一轮,对每个可能的猜测计算「最坏情况下它能留下多大的候选集」,选那个最坏情况最小的猜测。

这个 minimax 选择保证无论密码是什么,五次猜测内必中。1296 个候选在极小极大策略下被五层劈干净。

直觉上它偏爱「信息面宽」的猜测。开局猜 1122 这类带重复的,各反馈分支最均匀,最坏分支最小。

后续研究用信息熵替换极小极大(挑期望上把候选集劈得最碎的),平均步数再降零点几,但五步上限的优雅至今没有更简洁的替代。

反馈本身也有一段值得拆开的算法。

黑钉的计数是平凡的(逐位比对),白钉的计数却容易写错。带重复颜色时,「颜色对但位置不对」不能逐位独立判断。

干净的两遍法是:第一遍数黑钉,把已配对的从两边各划掉一份。第二遍对剩下的猜色与密码色各建一张计数表,白钉 = 每种颜色在两边计数取小再求和。

这几行算术是整个游戏的「物理引擎」。反馈的确定性全部由它保证,Shannon 意义上的信息流也经它度量。

六色四位的反馈空间只有 14 种可能出现(15 种黑白组合里,3 黑 1 白不可能出现)。一次猜测至多劈出 14 个分支,log⁡214≈3.8\log_2 14 \approx 3.8 比特就是单次猜测的信息上限。

log⁡21296≈10.3\log_2 1296 \approx 10.3 比特的未知量,理论下界恰好是三轮。Knuth 的五步与这个下界之间隔着的正是分支不均匀的代价。

把 Mastermind 放回「机械化」的主线上,它补充了斑马谜题缺的那一环:当线索不能白拿时,演绎的核心决策从「怎么推」变成了「怎么问」。

每一次猜测既是试探又是实验设计。好的猜测未必接近答案,而是无论答案是什么都能把可能世界劈开。

这个「实验设计」的视角,往上通到二十个问题的信息论下界,往下通到主动学习的一切算法。

这套机制的可移植性也远超游戏本身。数字版的「猜数字、返回几个位置对几个数字对」(牛牛游戏)是同一台机器换了个 alphabet。近年流行的猜词游戏把候选集换成词表、反馈换成「绿黄灰」,本质仍是 1296 个候选上的过滤与劈分。Knuth 的框架照单全收,只是每个候选从四元组换成了单词。

换掉猜测的对象,留下劈分可能世界的骨架。这是 Mastermind 比「猜颜色小游戏」五个字大得多的部分:它是主动学习的最小完整模型,每一轮选择「期望信息最大」的实验,用最少轮数把假设空间收窄到唯一。

逻辑推理合集

前两者的共同信仰是「解唯一」。

逻辑推理合集把镜头转向另一个方向:它的五类练习(类比、数字、数列、图形与空间想象)每一类都在问「下面最像什么规律」。

这看起来是演绎,严格说却是归纳。

数列 2、4、8、16 的下一项,逻辑上是任何数。拉格朗日插值保证任意 n 个已知项都能被某个 n−1 次多项式唯一穿过,想让下一项是 33 还是 1024 都有严格的多项式伺候。

我们答 32 不是推出来的,是奥卡姆剃刀选出来的。在所有能解释已知项的规律里,「最简单」的那个赢。

理论上的严格版叫所罗门诺夫归纳:给更短的生成程序分配更高的先验概率。「翻倍」比某个精心构造的高次多项式短得多,所以 32 胜出。

注意「最简单」并没有做到数学上的唯一化(柯尔莫哥洛夫复杂度不可计算),归纳的合法性是实用主义的,不是定理级的。

这不是抬杠,而是三种游戏难度本质的分水岭。

斑马谜题的难度在线索组合深度。每条线索单独弱(只排除一两个可能),必须三条五条叠加才出必然结论,叠加的顺序与记忆负担就是难度。它测的是工作记忆与传播的勤奋。

Mastermind 的难度在信息购买策略。推理本身(过滤候选)是机械的,难在每一步「问什么」。它测的是实验设计。

逻辑合集的难度在归纳空间的大小。候选规律无穷多,「最简单」没有严格定义(柯尔莫哥洛夫复杂度不可计算)。练习真正训练的是对常见模式族的熟悉度:等差、等比、斐波那契、交替、嵌套。它测的是模式库,不是逻辑本身。

五类练习各自的难度机制也值得点名,因为它们调用的能力并不相同。

类比考关系映射(把「A 之于 B」的关系原样搬到「C 之于?」),难在关系的抽象层级。形状变化是浅层,数量与方位的组合关系是深层。

数列考模式族识别,难度即上文说的归纳空间。

图形推理考旋转、对称、叠加等视觉算子,是人类独有的并行处理强项。

空间想象考三维展开与心理旋转,它的难度随自由度个数陡增。

同一块「逻辑」招牌下挂着四种不同的肌肉。这也是为什么有人在数列上如鱼得水、在图形上一塌糊涂。不是智力高低,是模式库的分布不同。

所以拿「逻辑」当统称其实冤枉了斑马谜题和 Mastermind。那两个才是纯演绎,逻辑合集反而是三者中最依赖「见过世面」的。

三类游戏的难度账

收拢成一张表:

游戏线索来源演绎核心难度支点
爱因斯坦谜题一次给全 14 条传播(打叉)+ 搜索线索叠加的深度与记忆
珠机谜阵每次猜测购买候选集过滤 + 极小极大信息购买的设计
逻辑推理合集题面即全部归纳(模式族匹配)候选规律的空间

三者的训练可迁移性排序是清楚的。逻辑合集练的是模式库广度,迁移面最宽,阅读理解、代码 review 里的坏味道识别都吃这个。Mastermind 练的是「设计实验花最少的钱买最多的信息」,直接迁移到调试(先打哪个日志、先二分哪段代码)。斑马谜题练的是长推理链的严谨执行,迁移面最窄但最纯粹,它几乎是「能不能在十步推导里不糊涂」的标准测验。三种游戏各玩一段,比任何「逻辑思维训练课」都便宜且诚实。

最后留一个贯穿三者的检验标准:你的推理能不能被别人逐步复验。斑马谜题的每一步可以讲给朋友听、被逐条挑错;Mastermind 的每次猜测可以问「这个选择 Worst case 多大」;逻辑合集的答案则常常只能「你再看看」,归纳的结论拿不出推导过程。能被逐步复验的推理才配叫机械化——这也是为什么形式化值得被摆到所有「聪明」之前:它不是把推理变得死板,而是让推理第一次变得可讨论、可纠错、可积累。

推理游戏之外,纸笔谜题把「机械化」做得更极致:规则少到一分钟能讲完,却要求解唯一,逼你把每条规则都用到极限。

六种纸笔谜题的记账术

纸笔谜题是一个把「规则经济学」做到极致的品类。每道题只给两三条规则,却要求解唯一。规则少到一分钟能讲完,唯一性又逼你把每条规则都用到极限。

于是人类的解题过程变成一门记账的手艺。不穷举、不碰运气,而是维护一张不断收窄的候选账本,直到账本里只剩一个答案。

本节拆解六种经典纸笔谜题的规则内核与解题骨架,每一种都对应站内一个可玩组件,各组件的判定细节以源码为准。动手版本:骨牌谜题、帐篷谜题、Kakuro、连连看、水管工、点格棋。

多米诺配对

多米诺配对(Dominosa)的盘面是一张填满数字的网格,玩家要把整张网格切成互不重叠的双格骨牌。点中一个格子再点相邻格子,两格就连成一张骨牌,再点一次拆开。

经典规则里还藏着一条约束:同一种骨牌至多出现一次。

骨牌的种类由两格数字的无序对定义。数字 0–60\text{–}6 能组成 28 种无序对(含双同牌),4×44\times4 盘面切出 8 张骨牌,8 张必须互不相同。

站内组件做了四道预置题:两道 4×44\times4、两道 20 格长方形,数字范围 0–6,配新游戏、下一题与检查三个按钮。

判定逻辑值得单独看一眼。源码里的检查只验证覆盖完整性:已放骨牌数等于总格数一半就算通过,并不校验骨牌是否重复。组件接受的答案空间比经典规则宽,「随便盖满」也能亮绿灯。

但按经典规则玩的人不会停在这里。唯一性约束才是这个谜题的发动机。判定从宽、解题从严,乐趣反而一分不少。这也是纸笔谜题电子化的常见分工:机器管覆盖,人管严谨。

解题骨架的核心是配对账本。

用一个 2×32\times3 的迷你盘看它怎么运转:第一行是 1,1,21,1,2,第二行是 2,2,12,2,1。

2×32\times3 的全部切法只有三种。全竖切,得到 (1,2)(1,2)、(1,2)(1,2)、(2,1)(2,1)。上横下横加右竖,得到 (1,1)(1,1)、(2,2)(2,2)、(2,1)(2,1)。左竖加两横,得到 (1,2)(1,2)、(1,2)(1,2)、(2,1)(2,1)。

第一种和第三种都出现了两张 (1,2)(1,2),撞车,划掉。只有上横下横加右竖的三张互不相同。账本上排除完重复组合,解自动浮出。

实战里这笔账按格滚动。某个数字只剩最后一个未配对实例时,它的配对对象被唯一确定。某种骨牌一旦落账,所有能让它复现的格位组合全部作废,与这些格位争位的另一种骨牌自动上位。

账本每划一行,相邻格子的候选集就收缩一截。纸笔玩家的功力,全看账本维护得勤不勤。

账本还能再省几笔。

角格是天然的突破口。角落里的数字只有两个邻居,配对候选天然减半。若它的两个邻居数字还相同,账本上立刻写下「这张骨牌的组合确定、方向待定」。方向不定但组合已定,足以排除别处对这种骨牌的使用。

双同牌((0,0)(0,0)、(1,1)(1,1) 这类)是另一种便宜的账目。数字 0–6 只有 7 种双同牌,复现一种双同牌需要盘面上恰好存在相邻的同数字格位,这类格位稀少,落账之后整片区域都可以划掉。

反过来算一笔稀疏账。4×44\times4 切 8 张牌,候选却有 28 种,解只占候选集的 8/288/28。约束的供给远大于需求,也就是好题「必然存在唯一解」的算术底气。

组件的交互也贴着记账逻辑设计。每个格子被占后不能再入第二张骨牌(每格度数上限一),误连再点一次即拆除。电子账本允许涂改,纸质账本不行,这是电子化送的福利。

从图论视角看,切割是网格图上的完美匹配,4×44\times4 盘面就有 36 种覆盖方式。但「每种至多一次」把单纯的匹配问题升级成匹配加记账的组合问题,账本才是那个把几十种覆盖压成一条线的工具。

树桩帐篷

帐篷谜题(Tents)的规则有三条。

盘面上已种好若干棵树,你要在空格里放上同样多顶帐篷。每顶帐篷必须与自己的树正交相邻(上下左右贴着)。任意两顶帐篷不能相邻,注意是八邻域,连斜角相碰都不行。

行首与列首的数字给出该行、该列的帐篷总数。

站内组件是四道 6×66\times6 的题,每道恰好 6 棵树、6 顶帐篷,点击空格放置或移除,帐篷数与树木数常驻在顶栏。

实现细节有个值得停顿的地方。实时标红的校验只有两条:行列计数对不上、帐篷互相接触。而「帐篷必须贴着自己的树」这条没有独立的过程校验,最终判定按预置的唯一解收口。

机器替你盯着两本账(行账与列账、禁区账),第三本账(帐篷与树的配对)要自己记。

这恰好点破了纸笔谜题训练的分工:规则写得越少,人要维护的隐含约束越多。

人类解题的骨架分四步。

第一步,计数封口。某行已放帐篷数等于行提示数,该行其余格全部划空,列同理。计数账最先归零的行列最先「关门」。

第二步,必然位。一棵树周围的可放格如果被行列计数与禁区挤得只剩一个,帐篷落定,同时这格从其他树的候选里除名。

树与帐篷本质是一张二分图的完美匹配。每棵树对应一顶帐篷、每顶帐篷认领一棵树,匹配一侧的必然性会传染给另一侧。

第三步,禁区传播。放下一顶帐篷,它周围八格全部变成禁区。这个连锁常常直接把另一棵树逼出唯一解,或把某一行/列的剩余可放格压缩到与剩余计数恰好相等。

第四步,双账交叉。某行还差一顶帐篷、某列还差一顶帐篷,两个「还差」的交集若只剩一个格子,答案就是它。

四步循环往复,每一步都在给候选划线。6×66\times6 的盘面上,人类从来不需要「试」,只需要记账。

计数还有一条全局校验值得开局先做。行计数之和必然等于列计数之和,且都等于树数。站内四道题都是 6 棵树,行列合计各 6,三本账互为对账单,先对一次账能提前拦下看错行、看错列的低级失误。

位置上也有两处捷径。角落里的树只有两个候选格,非角的边缘树只有三个,它们的帐篷几乎总是最先落定。反过来,若某棵树的全部候选格都挤在同一行或同一列,这棵树的帐篷会立刻消耗那一行/列的一个名额,行列账跟着联动。

把这三类捷径串成开局三步:对账、锁角、数边缘。通常不用十步就能把 6×66\times6 推到只剩两三处待定,剩下的交给双账交叉收尾。

数和

数和(Kakuro)是数独的加法表亲戚。

白格按横向、纵向连续成段,每段有一个提示数,段内填 1–9 使和恰好等于提示。经典规则还要求段内不重复。

规则只有这两条,但解题用的表是组合数学的。kk 个互不相同的数字之和的范围是:

k(k+1)2≤s≤k(19−k)2\frac{k(k+1)}{2} \le s \le \frac{k(19-k)}{2}

下限是 1+2+⋯+k1+2+\cdots+k,上限是 9+8+⋯+(10−k)9+8+\cdots+(10-k)。

这个范围表是数和全部火力的来源。越靠近两端的和,可能的组合越少;落在中间的和,组合数最多。组合数沿和轴的分布中间隆起、两端坍缩,形态与正态曲线如出一辙。

出题人把「信息量」藏在了离中心越远越好的地方,解题人则要先把这条曲线刻进脑子。

组合拆分的四门基本功由此展开。

第一门,极值锁死。两格之和 17 只能是 {8,9}\{8,9\},之和 16 只能是 {7,9}\{7,9\},之和 3 只能是 {1,2}\{1,2\}。贴着范围端点的提示直接把整段数字钉死。

第二门,小和穷举。三格之和 6 只能 {1,2,3}\{1,2,3\},之和 7 只能 {1,2,4}\{1,2,4\},之和 8 有 {1,2,5}\{1,2,5\} 与 {1,3,4}\{1,3,4\} 两种。小和段的组合数极少,一段三格和 6 等于白送三个已知数。

第三门,交叉过滤。横段与纵段在某格相交,该格的候选必须同时落在两个段的组合集合里,取交集后常常只剩一个数字。

第四门,余量记账。段内已填部分确定后,剩余格子的和被压缩成一个更小的子问题,组合表逐层下钻。三格和 12 填了一个 5,剩下两格和 7 只能是 {1,6}\{1,6\} 或 {3,4}\{3,4\},再与交叉列的候选一碰便见分晓。

组合表之外还有一条全局恒等式。段内不重复时,1 到 9 的总和恒为 45,任何覆盖九格的整段之和必是 45。标准盘面上的「45 法则」是杀手级的:一条只差一格填满的整段,最后一格等于 45 减去已知部分。

5×55\times5 的小盘用不上整段 45,但恒等式的思想照样可用。交叉格被横纵两段同时记账,两边的方程各给一遍答案,对上了账才算数。两格和 10 与两格和 12 交叉,横段另一格是 3、纵段另一格是 5,那么 3+x=103+x=10 与 x+5=12x+5=12 给出同一个 x=7x=7,账平了。对不平就说明某处填错了,回头查。

数和的解题体验因此是「组合表 × 方程组」的双重记账,代数味比其余纸笔谜题都浓,也更接近数独谱系里「唯一候选」的口味。

站内组件是一道 5×55\times5 预置题,数字键盘填 1–9,可清除、可重置,也提供显示答案。另有随机模式,生成的是不带提示数的随机盘面,更像一块演算沙盘。

界面把盘面与输入做足了,推理账本仍然在纸上。这倒也忠于「纸笔谜题」的本义。

这个品类的出题比解题难。先要保证每段提示在组合表里有唯一落点,再保证全盘解唯一,最后还要把提示挖得恰到好处。随机生成的盘面几乎不可能同时满足这三条,所以真正可推理的题永远是人工标定过的。

连连看

连连看(按二人麻将 / Shisen-Sho 规则实现)的规则一句话讲完:点掉两张相同图案的方块,条件是它们之间能画出一条至多两次转弯的通路,且通路中间只穿过空格。

站内盘面 8×68\times6 共 48 张、10 种图案两两成对。棋盘外圈垫了一圈虚拟空格,路径可以绕出边界外。这是实现里的经典技巧,把「从边缘绕过去」变成合法通路,也常是僵局的救命通道。

两次转弯是整个游戏的灵魂边界。

零拐:同行或同列,中间全空,直连。

一拐:L 形,拐点处两段直线各自畅通。

两拐:Z 形或 U 形,等价于「存在一条横向或纵向的走廊作为中转」。

源码的判定做成了在(格子、方向、拐弯数)三元状态上的广度优先搜索。从起点向四个方向出发,沿直线前进只走空格,拐一次弯计一次数,两次转弯之后只能继续直行。把「路径至多两拐」翻译成状态机的转移上限,比枚举所有两拐路径优雅得多,也顺便保证找到的一定是拐弯最少的通路。

提示按钮的实现则朴素得多。双重循环穷举所有同图案格对,对每对调用同一个判定函数,找到第一对可连的就选中其一。

同一个函数,既是通路检查器也是死局探测器。没有任何一对可连时,盘面就死了。

生成器只保证图案成对、不保证全程可解,死局是真实存在的状态。这也是连连看与本节其余谜题的气质分野:其余五种每关预置唯一解,连连看是过程游戏,随机牌序既可能一路畅通,也可能中途卡死,唯一的出路是重开。

解题骨架是三层扫描。先扫零拐对(相邻或同线真空的),再扫一拐对,最后才动用两拐。因为每一次消除都在改写空格分布,零拐对的成本最低、收益最确定。

真正的决策点在于同图案多对时的选对。10 种图案往往每种有两三对并存,消哪一对决定了给后续路径腾出哪条走廊。新手见对就消,高手先看这条通路对未来局面有没有用。把「消除顺序」当成「空间资源分配」来想,是这类游戏从休闲跨入策略的分水岭。

再算一笔状态空间的账,就知道这个判定为什么便宜。盘面连外圈共 8×10=808\times10=80 个位置,每格 4 个行进方向、拐弯数 0–2 三档,状态总数不足千个,广度优先搜索毫秒级出结果。提示按钮的暴力穷举是 (482)\binom{48}{2} 量级的格对、每对跑一次同样的搜索,对网页小游戏而言依然瞬间完成。

「至多两拐」本身也是个精心挑选的难度阀。一拐太松,同行列就够,游戏退化成眼力测试。完全不设限,判定退化为普通的图可达性,空格一多几乎总是可连,决策深度归零。恰好两拐时,通路存在性同时依赖图案分布与空格形状,消除顺序才真正开始影响后续。

「路径只许穿过空格」还顺带把空走廊变成了资源。留一条通道,有时比多消一对牌更值钱,这与点格棋「留链」的思路遥相呼应。

水管工

水管工的盘面是 6 列 ×\times 5 行的管件网格,四种管件:直管、弯管、三通、十字。左缘某行是水源(绿框),右缘某行是出水口(红框)。

点击任意管件顺时针旋转 90°,目标是让水从源流到汇。

判定不是「看起来连上了」,而是图遍历。从源出发做深度优先搜索,水流只沿双向咬合的邻格扩散。这格朝东开了口还不够,东邻必须朝西开口,单向的开口等于堵死。到达汇格、且汇格开口朝右,才算连通。

双向咬合是新手最容易栽的规则。直觉是「我这格朝你开了口,水就该过去」,但实现里开口是一份合同,双方签字才生效。死路不在自己身上,在对面没回应。

由此推出解题骨架的三原则。

其一,别看开口,看对面的回应。检查任何一段连接前,先确认邻格的反向开口存在。

其二,自由度排序。直管与弯管的有效朝向最少(直管 2 种、弯管 4 种),三通有 4 个朝向、十字根本不用转。先把低自由度的格子锁定,尤其是边界格:顶行的一根直管若要参与连通,朝外的开口毫无用处,它必须平行于边界躺着。把高自由度的三通和十字留到最后,这是约束求解里最小剩余值(MRV)启发式的手工版。

其三,两端向中间推。源与汇的格位是盘面上最强的两条已知约束,从这两头确定必经格、向外传播,比在盘面中央瞎转快得多。

生成器保证每关有解。源汇之间的路径是先铺好的,路径格按路径走向固定为直管或弯管,只是朝向被打乱,其余格子完全随机。

但注意判定条件比「还原预埋路径」宽。DFS 可达加汇格开口朝右即通过,而那些随机铺开的非路径格同样参与连通。你用三通绕出另一条路线,一样亮「连通!」。

生成保证有解、判定从宽,让组件玩起来没有挫败感,却也意味着它锻炼的是连通分析而非唯一解推理。与多米诺配对恰好站在纸笔谱系的两端:一端逼你找到那个唯一的账本闭合,一端只要求你修出任何一条活路。

旋转操作本身也有账可算。每格朝向只有 4 个旋转位,点击是循环。直管的正确朝向 4 次点击内必达,弯管、三通同理,十字点不点都一样。试错是有限循环,不存在死锁,真正的成本只在于哪一格值得先转。

这催生一个朴素的优先级。边界格(朝外的开口是浪费)最优先,源汇邻格次之(强约束),普通格再次,十字垫底(免转)。

另外注意判定用的是从源出发的整链 DFS,部分连通没有任何反馈。水要么从源一路通到汇,要么整条链默默无效。人类玩家要自己维护「哪片区域已经咬合成网」的心智图,从源出发逐格扩展、每扩一格就回头检查新格的其余开口,这就是水管工版的记账。

点格棋

点格棋的规则几乎没有门槛。轮流在相邻点之间画线,谁画下围成一个方格的最后一条边,方格归谁,并且再走一次。所有方格归属完毕,多者胜。

站内组件用 4×44\times4 点阵围出 3×33\times3 共 9 个方格、24 条边,支持人机与双人。电脑的走法是从所有空边里均匀随机抽一条,一个诚实到可爱的对手。

但「完成得格再走一次」这条规则是整个博弈论的火种。

它意味着棋盘上一旦出现三边已围的格子(缺口),吃掉它的人可以连吃。缺口相连的一串方格构成一条链,谁被迫先画下链上第三条边(开链),对手就能一路吃到链尾。

于是整盘棋的宏观结构只剩两个问题:链是怎么形成的,以及谁被迫开第一条链。

前期的多数着法都在避免制造缺口(安全手),安全边耗尽的那一步就是「开链权」的交接仪式。3×3 小盘边数少,这个交接来得早,每一步安全手都值得慢下来数缺口。

让子(sacrifice)与双交(double-cross)是接住交接的技艺。

当对手开链、你面对一条长 L≥3L\ge3 的链时,标准答案不是全吃。吃掉前 L−2L-2 格之后,画下链尾「双交」的那条边。放弃最后两格,让对手拿走这两格并被强制开下一条链。

账面核算:你得 L−2L-2、失 2,换来的是把「下一个开链的人」锁定为对手。只要盘上还剩别的链,这笔交易稳赚。

环形链的让子价码是 4 格。

让子策略的精髓是把方格从「目标」降级为「货币」。格子是买开链权的钱,胜负手不在这一轮吃多少,而在最后一轮交割之后,谁手里还捏着开链义务。

链条形成期还有一门慢棋功夫值得记。24 条边里,前期每一手都该维持「每个方格至多两条边」。这是安全手的图论定义:一旦某格被画到第三条边,缺口就出现了。

双方于是都在往一张度数受限的图上添边,安全手耗尽的时刻由图的结构决定、不由意愿决定。小盘上这个时刻来得很快,高手要做的其实是提前两三手布局:控制到时候出现的是长链还是短链、是链还是环。

9 个方格是奇数,先手握有 parity 红利。双方都走出最优着法时,先手以 5–4 取胜。后手的应对是把局面切出偶数条链,让「最后一个开链」的义务落回先手头上。

让子技艺的全部前提,都在这几手安全棋里铺好了。

需要说明的是,这套技艺在对随机电脑的对局里毫无用武之地。均匀随机的对手不懂得接受你的好意。点格棋组件的价值在双人模式里演示这套规则骨架,竞技级的让子博弈需要更大的盘面,和一个同样清醒的对手。

六种谜题摆完,记账术的集大成者是数独:它有最完整的技巧阶梯,也有最工业化的出题机器,两件事互为镜像——上半场讲人怎么解,下半场讲题怎么造。

数独的两面:从技巧阶梯到出题机器

数独规则极简:每行、每列、每宫 1–9 各一次。

但”会玩”和”能解难题”之间是一条清晰的技术阶梯,而每一道被玩过的题,背后都站着一个保证它”可解且唯一”的生成器。

这两件事互为镜像:人类的技巧阶梯定义了题目的难度,机器的唯一性判定决定了题目是否成立。

练习盘面在数独,支持草稿候选数模式。下面的技巧全部建立在候选数标记上。

第 0 级:唯一余数与隐性唯一

  • 显性唯一(Naked Single):某格的候选数只剩一个。通常是行/列/宫的排除自然积累的结果,是数独的”呼吸”。大部分填空由它完成;
  • 隐性唯一(Hidden Single):某格候选数有好几个,但某个数字在这一行/列/宫里只有一个可能位置。它藏在候选数堆里,需要按”数字”而非按”格子”扫描。隐性唯一是绝大多数入门题的主力技巧。

入门题(报纸级)通常只靠这两级就能通关。

第 1 级:区块排除

当数字 k 在某个宫里的候选位置全部落在同一行时。虽然还不能确定是哪格,但可以确定的是:这一行的其他格都不可能是 k(否则该宫的 k 就跑出行了)。

这个”整宫限制行”的推理叫 Pointing;反过来”整行限制宫”叫 Claiming。

区块排除本身不直接填数,它的作用是删候选。

而候选删得干净,第 0 级的显性唯一就会批量出现。

中级题的命脉在这里。

第 2 级:数对与三链数

  • 显性数对(Naked Pair):同一单元(行/列/宫)里有两格,候选都是同样的两个数字 {a, b}。那么 a 和 b 必被这两格瓜分。单元里其他格的候选可以删掉 a 和 b;
  • 隐性数对(Hidden Pair):某单元里两个数字只可能出现在相同的两格(哪怕这两格还有其他候选)。则这两格除 {a, b} 外的候选全删;
  • 三链数(Triple)是数对的推广:三个数字被三格瓜分,道理相同。

数对家族可以用一句话统一起来:显性是”n 个格子只装得下 n 个数字”,隐性是”n 个数字只住得进 n 个格子”。

两者互为行列视角的对偶,删除逻辑各自相反(显性删单元内其他格的这 n 个候选,隐性删这两格的这 n 个之外候选)。

三链数、四链数是同一句话的 n=3、n=4 版本,实现上就是”在单元内枚举候选集合的组合”。k=2 到 4 的组合数不多,人工扫描勉强可行。k≥5 时枚举量与观察成本一起爆炸,实战价值骤降。

理解了这个统一结构,数对和三链数就不用当两条规则背。

它们是同一条规则的两个方向。

这是第一次需要”同时持有多个格子的信息”的技巧,观察难度明显上了一个台阶。

第 3 级:链式结构

到这里,格子间的推理开始出现”远程作用”。

X-Wing:数字 k 在两行中的候选位置都恰好是相同的两列。

无论 k 最终怎么摆(两种摆法二选一),这两列的其他格都不可能是 k。

一个 2×2 的矩形约束,跨越四个单元发力。

举个例子:数字 5 在第 2 行的候选只落在第 1、8 列,在第 7 行的候选也只落在第 1、8 列。矩形四角成立。

若第 2 行的 5 在第 1 列,第 7 行的 5 就被逼到第 8 列;反之亦然。

无论哪种,第 1 列和第 8 列其余格子里的候选 5 都可以划掉。

Swordfish 是它的三行版本(三行三列),原理相同、观察难度更高。

XY-Wing:三个格子构成铰链。

枢轴格候选 {x, y},两翼格分别候选 {x, z} 和 {y, z}。

两翼都与枢轴同单元,且两翼共同”攻击”某个目标格(都能看到它)。

无论枢轴是 x 还是 y,两翼中必有一个变成 z。

所以目标格可以删掉 z。

这是第一次出现”如果…那么…无论哪种情况”的二维推理,是从”数数”到”逻辑链”的分水岭。

第 4 级之后

更强的技巧(Simple Chain、X-Chain、着色法)把”强弱关系”串成长链:沿链传递”必真/必假”,在链的两端收敛出删除。

它们形式统一、威力强大,但观察成本也最高。

大多数人工玩家到 XY-Wing 为止已经能覆盖中级与大部分难题,链条技巧留给发烧友。

这之后还有一类走法不太一样的技巧:唯一性假设。

代表是唯一矩形(Unique Rectangle):若某四个格子构成的结构允许两种都合法的摆法,题目就有两个解。

与”数独题目唯一解”的约定矛盾,所以可以放心删候选。

注意它的立足点不是数独规则本身,而是”题面承诺唯一解”这个出题习惯,题库不保证唯一性时不能用。

技巧阶梯走到深处,连”哪些推理前提可以借”都成了一门学问。

而这个”唯一解”的承诺,正是下半场机器要拼命保证的东西。

卡住时的全盘扫描顺序

“卡住”多数时候不是不会技巧,是扫描没有纪律。

眼睛随机乱看,漏掉某个单元里的隐性唯一,然后在错误的地方死磕。

值得把扫描做成固定流水线,每轮从头到尾:

  1. 显性唯一:扫全部空格,候选只剩一个的直接填;
  2. 隐性唯一:按数字扫(1 到 9 逐个),在每个单元里问”这个数字还能放哪”;
  3. 区块排除:每个数字 × 每个宫,看候选是否锁进一行/一列;
  4. 数对/三链数:每个单元里找候选集合相同的格子组合;
  5. X-Wing 及以上:按数字扫全盘的行列候选位置。

任何一步有产出,回到第 1 步重新来(新数字会引发连锁)。

五步全空手而归才承认”真的卡住”,需要更高级技巧或回头检查有没有标错候选数。

固定顺序的价值在可复现:同一块棋盘扫两遍结果一样,“漏看”从玄学变成流程错误,改流程就行。

配合两条习惯:全程开着草稿模式把候选标全(后面所有技巧都依赖候选的可见性)。刻意记录自己卡住的那类僵局。多数人卡住的总是同一两种结构,针对性补那一级最划算。

换位:计算机怎么解同一道题

有意思的对比:计算机解数独根本不用这些技巧,直接回溯 + 约束传播。

选候选最少的格子,试填、传播、冲突回退,几毫秒解完任何 9×9。

那人类技巧图什么?

图的是信息结构的可读性。

回溯是”试错 + 撤销”,逻辑技巧是”每一步都确定地为真”。

人脑没有栈,撤销的成本极高,所以需要每步都不可撤销的推理。

两者的关系类似证明与搜索:计算机找到的答案和一个优雅的证明是两回事。

数独题的”难度分级”其实是按所需技巧阶梯标的,而不是按回溯步数。

这也是题库分级比”程序认为的难度”更贴近人类感受的原因。

而这个对比直接通向下一个问题:机器既然解题这么快,题是怎么被造出来的?

答案里最重的机器,恰好是”解法”的一个变种。

生成流程

数独生成是”逆向”的:不是从空盘出题,而是先造一个完整合法终局,再拆掉一些格子。

第一步:造终局。

从空盘开始,用回溯法逐格填数,每个格子的候选数字随机打乱顺序再试。

随机性让每次生成的终局不同。

回溯在这个问题上快得惊人:约束传播(候选数过滤)加最少候选优先(MRV,每步都填候选最少的格子),毫秒级就能填满。

九宫的约束用位掩码表示(见下一节),一次位运算完成候选过滤。

第二步:挖洞。

从终局里逐个移除数字,每移除一格就问一句:剩下的题目还有唯一解吗?

有:保留这格的空洞。

无:把这格的数字放回去,换下一格。

挖到目标空格数为止。

整个生成器的质量取决于第二步的判定器。

心脏:解数计数器

判定唯一性的朴素想法是”解一次题,看能不能解出来”。

这是错的。

解出来只说明”至少一个解”,说明不了”只有一个”。

正确的做法是把解题器改造成解数计数器:

countSolutions(board, limit=2):
找到候选最少的空格
if 没有空格: return 1 # 又找到一个完整解
for 每个候选数字 d:
填入 d
count += countSolutions(board, limit)
撤销 d
if count >= limit: return count # 剪枝:够数就停
return count

三个要点:

  1. 数到 2 就停。唯一性判定只关心”是 1 个还是不止 1 个”,找到第二个解立即层层返回。这道剪枝让”多解题”的判定非常快,反而比”完整解一次”更省;
  2. MRV 剪枝(最少候选优先)同样是计数器的效率来源:先分叉少的格子,搜索树最小;
  3. 判定结果只有三种用途:1 = 可挖、≥2 = 必须放回。没有第三种情况(0 不可能。挖洞前的题目来自有解的母题)。

一台生成器每道题要跑几十上百次计数器,好在单次微秒到毫秒级,实时生成无压力。

位掩码

支撑这一切快得离谱的地基是位掩码。

三个长度 9 的整数数组分别记录每行、每列、每宫”已出现过哪些数字”,第 k 位是 1 表示 k 已用:

candidates(r, c) = ~(rows[r] | cols[c] | boxes[box(r,c)]) & 0x1FF

一次”或”拿全三个约束,一次”取反与”得到候选集。填数 = 对应三处置位,回溯撤销 = 清位。

传统写法里”逐个检查 9 个数字是否冲突”的九次比较变成一次位运算。

选格子用 MRV,需要数候选个数(popcount),取最低位候选用 lowbit 技巧(x & -x)。

数独的约束结构天然适合位掩码:27 个约束单元、9 种状态,全部对齐到 9 bit。

生成器”毫秒级出题”的底气,一半在回溯剪枝,一半在这里。

唯一解是数独的灵魂

为什么如此执着于唯一解?

因为人类解题的全部技巧都建立在”每一步推理必然为真”上。

多解题里,某些格子在不同解中取值不同。针对它们的任何”确定性推理”(隐性唯一、数对、X-Wing)都会失效,玩家只能靠猜测推进。

一道多解的”数独”在玩法上是另一个游戏:不是逻辑谜题,而是运气的填字。

上半场唯一矩形技巧借用的”唯一解承诺”,就是在这里兑现的。

所以严格的数独题库(包括各类竞赛)都把唯一解写进定义。

这也是”程序生成”与”人工出题”的分界:人工出题靠经验和验证,程序生成把验证做成了每一步的硬性门槛。

挖洞数量 ≠ 难度

一个流传很广的误解:挖得越多越难。

真相是难度与空格数弱相关,与”解题需要哪一级技巧”强相关。

两个极端例子:

  • 一道 50 空格的题,如果每一步都有隐性唯一可走,它是入门题;
  • 一道 30 空格的题,如果推到中途必须用 X-Wing 才能破局,它是困难题。

空格数本身倒有一条硬边界:一道唯一解数独最少只能给17 个提示数。

2012 年有团队用穷举计算证明了 16 个提示数不存在唯一解数独。

生成器挖洞时不用担心撞上它:判定器会在题目变成多解之前就把数字放回去,17 这个下限是”被判定器自然拦住”的,不需要专门编码。

难度模拟器

合格的生成器怎么控制难度?

数独难度靠数技巧,不靠数空格。

用”人类模拟器”解题,把上半场的技巧阶梯直接翻译成代码:

难度 = 0
loop:
用等级 ≤ 当前放行上限的所有技巧解题,直到推不动
if 解完: 难度 = 本轮用到的最高技巧等级; 结束
if 计数器判定无解: 题目作废(不该发生); 结束
放行更高一级技巧,继续

每一级技巧本身也是固定代码:显性唯一是”候选数只有 1 的格子”,隐性唯一是”某数字在单元内只剩一个位置”,区块排除、数对……逐级往上加。

模拟器卡住不等于无解。它只是”再往上的技巧还没实现”。

所以循环里要和计数器联动:计数器说唯一解、模拟器推不动,难度就是当前上限再加一级。

实现顺序也有讲究:把低等级技巧做全(漏一种隐性唯一的变体,难度就会虚高),比急着实现 X-Wing 更影响评分质量。

挖洞阶段甚至可以反过来用:每次候选挖除后用模拟器复评,难度不达标就继续挖或回滚。

这种”以解法分级”的思路和回溯步数分级(程序视角)是完全不同的两个刻度,前者才对得上人类玩家的感受。

对称与美感

出版物里的数独大多有旋转对称的洞型(挖掉的格子关于中心对称)。

工程上很容易:挖洞候选按对称对处理,挖 a 必同试它的对称格 a′。

代价是自由度变小。同等难度下,对称题能挖掉的格子通常略少。

这是个纯审美决策,但对”看起来像正规出版物”的观感贡献很大。

数独之外,还有四个最容易被当成”运气游戏”的谜题——2048、扫雷、点灯、滑块。它们各自藏着一件不需要天赋、只需要方法的数学工具。

四个谜题里的数学工具

有四个游戏最容易被当成”运气游戏”:2048(新块随机出现)、扫雷(雷是随机布的)、点灯(Lights Out,开局看起来毫无规律)、数字华容道(滑块谜题,随机打乱后一半永远拼不回去)。

但它们各自藏着一整套不需要天赋、只需要方法的解法。

2048 靠结构性策略驯服随机,扫雷把每一步赌注摆上桌面计算,点灯则干脆是一道伪装成游戏的线性代数习题,滑块谜题贡献了最优雅的一件工具:用一个”永远不变”的量判定可解性。

本节把四个游戏讲透,四种数学工具(策略结构、概率推理、线性代数、不变量)在同一个棋盘架子上轮番登场。四个游戏本站都有:2048、扫雷、点灯、数字华容道与经典版华容道。

一、2048

下面是可玩的 2048,边读边开一局,正文里的每条纪律都能当场验证。

先看清随机性

2048 的规则三十秒就能学会:4×4 网格滑动,相同数字相撞合并,每一步后随机出现一个新块。

但”能玩”和”稳定冲上 4096”之间隔着一层没写在规则里的东西:对合并顺序的规划。

每一步之后系统在随机空格里放一个新块,90% 概率是 2、10% 概率是 4(这是通行实现的比例)。

这个不对称有两个直接后果:

  • 2 是主粮,4 是跳级。合成一个 2k2^k 需要 2k−22^{k-2} 个 4 当原料。一个 128 就要 32 个 4;全用 2 当原料则要 64 个,所以绝大多数原料其实来自那 90% 的随机 2。4 的价值在于跳级:每随机出一个 4,等于省掉一轮”两个 2 合成一个 4”的工序,盘面上大数越多,这道工序越贵;
  • 理论上限的计算也卡在这里,后面会看到。

核心原理

2048 没有悔棋,而合并是信息递减操作:两个 64 变成一个 128,你失去的是”这里有两个可以分别调度的单元”。

新手最常见的死法是乱序合并。把一整行 2、4、8 合成一坨,突然发现棋盘上没有任何两个相邻的相同数字,一步棋产生的合并数为零,还要被迫再刷一个块。

高手的每一步都在维持一种结构:单调链。

把最大数钉在一个角落,让次大数紧挨着它、再次大数再挨着,整行/整列沿一个方向递减。

这样一旦链条末端合并升级,新的大数自动与链条的下一环相邻。合并像多米诺一样沿着链传递,一步可以触发连锁(128+128→256,顺势与旁边的 256 合并成 512……)。

角落策略

单调链需要一个锚点,这就是”把大数留在角落”的原因:

  • 角落只有两个方向可能被破坏(大数在角落,向角落滑动的操作永远不会把它拖离位置);
  • 实际操作上有两条纪律:不要为了小合并破坏大结构。一个孤立的 32 合并微不足道,为了它把 512 从角落里拖出来是灾难。保持至少一个”恢复方向”。四个方向里永远留一个能把全盘压回单调形态的(通常是”把一切推向角落”的那个方向),它不到关键时刻不按。

另一条容易被忽视的纪律:填满率比分数重要。

空格数量就是缓冲区。空格多,坏开局有救;空格少于三四格时,一次随机 4 出在错误位置就能终结。

所以中期要优先清理”碎块”(不成链的零散小数字),而不是急着冲分数。

蛇形链

严格递减的一行只有 4 格,而高分局里链上的数字远不止 4 个。

所以实战里的单调链从来不是直线,而是蛇形:沿第一行从角落递减到行尾,行尾折返,第二行反向继续递减。

比如第一行 512、256、128、64,第二行从右往左接 32、16、8、4,让 64 和 32 在行尾相邻。

这样 16 格串成一条 8 段折线,链上任何相邻两环都实际挨着。

蛇形链的等价摆法有 8 种(4 个角落 × 顺/逆时针两个绕向),本质相同。选定一种就别中途换向,两次换向之间棋盘必然经历”链断裂”的脆弱期。

蛇形还带来一条新纪律:新的大数要落在链的延长线上。

合并发生在链尾,升级出来的块要能顺势接入下一环。如果链尾附近堆着不成链的碎块,新大数反而被隔离成孤岛。

清理碎块的优先方向是把它们往链的反方向推,腾出链尾的走廊。

三种典型死法

复盘败局,绝大多数落进三个模式:

  1. 角落漂移。最大数只在你”推向角落”时保持不动。中途为了吃一个小合并按了侧向,它被拖出角落。重新钉回去需要重建整条单调结构,重建期棋盘无锚,随机块落点处处危险;
  2. 摇头死。链横在头两行时,上下方向的大动作可能拖走大数,于是只敢左右来回推。每一步都强制出一个新块,行结构却在持续退化,几步之内必然死锁。摇头不是失误,是链已经坏掉的症状:发现自己在摇头,就该考虑弃掉局部、重整结构;
  3. 提前合并。链条中段的一对相同数字被”顺手”合掉了,比如 256、128、64 的链把那对 64 合成 128。链从中间断开,前后两截对不上号。合并应该沿链尾进行,中段的合并机会宁可留着当备件。

与死法相对,还有个常被忽略的资源概念:对齐。

待合并的两对数字排在同一行(列)上,一次滑动可以同时触发两组合并,一步顶两步。

对齐散乱时,同样的合并要拆成两步走,每步多付一个随机块的代价。

一局棋的节奏

把”单调链 + 蛇形”落到一整局棋上,三个阶段的任务完全不同:

  • 开局(约前 30 步):目标不是分数,是把蛇形骨架搭起来。这个阶段大数字尚未定型,允许大胆合并、允许临时打破单调。真正要避免的只有一件事:让随机块在骨架成型前把棋盘塞满。开局空格多,犯错成本低,是唯一的”自由发挥期”;
  • 中盘:链成型后进入沿链尾推进的模式。每一步问自己两个问题:这次合并是否发生在链尾?合并后新块能否接入下一环?中盘的分数是副产品,真正的账本是”空格数 ≥ 4”和”链无断点”这两条;
  • 残盘(空格三四个以下):切换到生存模式。只使用”把一切推向角落”的方向,让随机块落在链的反方向。把攒下的合并机会留到链尾一次爆发。一次连锁清掉半排碎块,比分散的十次小合并多腾出成倍的缓冲。

判断自己处于哪个阶段,看空格数就够了:八格以上是开局,四到八格是中盘,四格以下是残盘。

很多对局死于”用开局的下法下残盘”。

一块具体棋盘的诊断

看一块中盘的局面,练习把上面的规则用起来:

512 256 128 64
4 8 16 32
2 4 2 4
8 4 2 4

第一行从左到右严格递减,第二行从右到左接 32、16、8、4。蛇形链完好在头两行,64 和 32 在行尾相邻。

下半盘的八个格子(2、4、8 一类)全部是不成链的碎块。

这块棋盘的诊断结论:结构健康,当前任务是清理碎块。

把它们朝下方归拢,腾出第三行给链尾延伸,而不是急着去合并上半盘的 128 和 256。

再看一块病板:

128 512 64 32
8 16 4 2
16 4 8 2
2 8 4 2

三处病灶:最大数 512 不在角落(第二列顶行,推右、推下都会让它继续漂)。次大数 128 在它左边,链的顺序已经颠倒,“压回角落”之前先得处理这个错位。下半盘的对子全部错开(16 对 16、8 对 8、2 对 2 都隔了一格),合并机会一步都触发不了。

这种局面下最优动作往往反直觉。先不管 512,把底部的对子对齐、攒出一次连锁,让腾出的空间给”重钉角落”留出操作余地。

诊断能力就是策略本身:同一块棋盘,看得出”链断在哪、缓冲还剩多少”的人,和只看总分的人,玩的是两个游戏。

运气成分有多重

由于新块位置随机,同样的策略也有方差。

把”某个固定策略下的最终棋盘”看作随机变量,可以谈期望。

社区大量实测的共识是:单调链 + 角落锚点的策略可以稳定地到 2048、大概率到 4096。到 8192 以上开始显著依赖运气(4 的出现时机)。

再往上,人手处理不了每一步的期望计算,需要 expectimax 搜索。把”我这一步怎么走”和”系统下一个块放在哪、是几”交替建模,对每个分支算期望效用再回溯。写出来的 AI 可以稳定到 32768 一带。

顺带说 expectimax 的叶子节点怎么给棋盘打分,因为那几类打分项恰好是人肉纪律的数学化:单调性(沿蛇形链方向是否递减)、平滑度(相邻格数值是否接近,决定眼前有多少合并机会)、空格数(流动性)、最大数是否压在角落,加权求和出一个分数。

开源 2048 AI 基本都是这个配方。

想提升人肉水平,照着这四项自查比背任何口诀都有效。它就是一份按重要性排好序的策略清单。

理论上限

社区公认的分析结论:4×4 棋盘上,理论上限是 131072(2172^{17}),而且达成条件苛刻到近乎幻想。

推理用”原料守恒”反推达成瞬间的棋盘长什么样。

合并出 131072 的前一步,场上必须有两个相邻的 65536,而要让这两个 65536”各自长成且互不消耗”,其余 14 格恰好要摆上从 32768 一路降到 4 的完整降序链。

两个 2162^{16} 加一条 2152^{15} 到 222^2 的链,16 格满盘,一格不空。

链条到 4 就到底:再往下摆一个 2 就是 17 格,棋盘装不下。链上任何一环缺位,原料总量立刻不够。

运气真正卡住的地方是最后一格:填满完美链的那个随机块必须是 4(概率只有 10%),出的是 2 就无法与任何块合并,完美结构当场作废。

所以 131072 是数出来的上界。全程结构完美,末尾还要抽中那一个 4,实践里当作不存在即可。人类记录常见落在 32768 到 65536 一带。

这个推理的通用价值在于方法本身:用原料守恒数上限。

把”达成瞬间最少占几格”算清楚就完了:合成 2k2^k 最少需要两个 2k−12^{k-1} 加一条降到 4 的链,共 k−1 格,所以 n×n 棋盘的上限是 2n2+12^{n^2+1}。

4×4 是 2172^{17},3×3 只到 2102^{10}(1024),5×5 抬到 2262^{26},棋盘多出 9 格,天花板翻了九番。

任何”合并类游戏”的天花板都可以照此数一遍。

规则一分钟,策略无穷深。这就是 2048 比看上去耐玩的原因。

2048 的随机性靠结构驯服。下一个游戏把随机性摆上台面,直接算账。

二、扫雷

扫雷是个被严重低估的数学游戏。

大多数人会数数字、会做基本推理,但一到”推不动了必须猜”的局面就听天由命。

其实扫雷中后期的每一步赌注都有可计算的风险,而比较这些风险正是高手和普通玩家拉开胜率的地方。

先分清两类推理

扫雷推理分两层:

  1. 确定性推理:某个格子必然是雷或必然安全。点错即死的局面里,能推出的确定性结论越多,需要赌的次数越少。
  2. 概率推理:确定性推完之后仍有多个选择,此时每个候选格子都有”是雷的概率”,你要选期望最优的那个。

绝大多数玩家只在第一层,第二层全凭感觉。

确定性推理的三个定式

数字 n 的含义是它周围八格中恰有 n 个雷。

最基本的操作是”余数减一”:如果一个数字周围已插旗的雷数等于 n,剩余相邻格全安全。如果未开格数等于剩余雷数,剩余相邻格全是雷。

在这之上,有两个经典的贴边定式:

1-2-1 定式(数字贴边一排 1、2、1,上方三个未知格):

□ □ □
1 2 1

三个未知格记 x₁、x₂、x₃(分别在三个数字上方,且都只与这三个数字相邻)。

三个约束:x₁+x₂=1,x₁+x₂+x₃=2,x₂+x₃=1。

第一式代入第二式得 x₃=1(雷),代回第三式得 x₂=0(安全),再回代得 x₁=1(雷)。

结论:两端的 1 正上方是雷,中间安全。不用背,三行消元自己推出来的。

1-2-2-1 定式:同样方法对四个数字列方程:x₁+x₂=1、x₁+x₂+x₃=2、x₂+x₃+x₄=2、x₃+x₄=1。

由第一式 x₁=1−x₂、末式 x₄=1−x₃,代入第二式得 x₃=1,代入第三式得 x₂=1。

于是 x₂=x₃=1(两个雷在两个 2 上方),x₁=x₄=0(外侧安全)。

定式只是方程组的速查表。

角上的 1:角落的 1 周围只有三格,若这三格全未开且无其他约束,任何一格都是 1/3。

这是扫雷里最便宜的”有约束盲区”。

这些定式网上都有总结图,但真正让你不再背图的,是下面这套统一方法。

概率推理

单约束的情形很简单:一个数字 n 周围有 m 个未开格,每个未开格是雷的概率都是 n/m。

一个”1”邻着 7 个未知格,每格 1/7。一个”3”邻着 4 个未知格,每格 3/4。

多约束才是常态。一个格子往往同时是几个数字的邻居,各约束互相牵制,这时单算就不对了。

统一解法是计数枚举:把互相牵连的未知格分成一组(通常是边界上的一串格子),枚举这组格子所有满足全部约束的雷配置。对组内每个格子,统计”它是雷”的配置数占合法配置总数的比例,就是它的后验概率。

用最小例子走一遍。

设三个未知格 a、b、c,两个约束来自相邻数字:1 周围只有 a、b(所以 a+b=1),2 周围是 a、b、c(所以 a+b+c=2)。

合法配置只有两种:(a=1,b=0,c=1) 和 (a=0,b=1,c=1)。

于是 c 是雷的概率 = 2/2 = 1(必是雷,直接插旗),a 和 b 各 1/2。

一枚举,单看任何一个数字都看不出来的结论就出来了。

1-2-1 定式不过是这个方法多一行方程而已。

完整的求解器还有两道工序,让计数枚举在真实盘面上跑得动。

一是组件切分:边界上互相牵连的格子天然分成几个独立连通块,块与块之间没有共享约束,各自枚举完再合并。总配置数是各块方案数的乘积,不用碰”全体边界一起枚举”的指数爆炸。

二是全局雷数约束:剩余雷数是硬上限。“块 A 用 a 颗雷 × 块 B 用 b 颗雷”的每种组合只有在 a+b+自由格雷数不超上限时才合法。

这一步会微妙地改动每块内部的概率。同一个格子,早局和残局的”名义概率”经常不一样,原因就在这。

组外格子也别忘了:不与任何已开数字相邻的”自由格”,概率等于全局雷密度(剩余雷数 ÷ 剩余总格数)。

初级是 10/81 ≈ 12%,中级约 15.6%,专家级约 20.6%。

这就是为什么自由格也不是白给的。专家级里一个自由格比贴着一个”1”的某些格子还危险。

必须猜时,点哪里?

把所有候选格子的概率算出来后,选择规则很朴素:点概率最小的。

但有两个经常被忽略的细节:

第一,概率相近时考虑”猜错后能不能继续”。

两个格子都是 50%,但赌其中一个输了游戏就结束,赌另一个输了还留有后续局面能赢回来。这时选”输了影响小”的那个。

最典型的就是终局的 50/50 二选一:没有信息能帮你,谁都一样。那就在游戏早期少犯确定性错误,别把自己逼进这种角落。

第二,降低未来方差也是一种收益。

有的格子概率略高,但点开后会释放大量信息(比如在信息交汇处),把后续多个 50/50 变成确定性推理。

严格的做法是把期望步数算到底,实用主义做法是:在两个概率接近的候选里,优先点那个”开盾”后能解开更多约束的。

还有一个流行说法值得澄清:“开局点中间比点角落好”。

现代扫雷实现大多保证第一击必安全(且常见实现还会让首击区域无雷),开局位置的差别没有传说中大。

真正影响首击收益的是开出的可推理面积,这取决于实现的首击保护规则,不同版本不一样。别把一个版本的规律当成普适。

3BV

比较成绩时有个比时间更公平的尺子:3BV(Board View Value)。

定义:盘面上连通”零区”(翻开时自动扩散的空白区)的个数,加上不与任何零区相邻的数字格个数。

它的含义是只用安全点击通关所需的最少点击数。零区一击整片打开,孤立的数字格必须逐格点。

3BV 把”这盘棋生得好还是生得差”量化了:一张 3BV 很低的专家盘,点十几次就结束。3BV 高的盘逼你逐格点完四百多个数字。

竞速玩家用”时间 ÷ 3BV”衡量操作效率,排除盘面运气。

写自动求解器时它也是现成的基准。求解器点击数逼近 3BV,说明它的”理解”已经贴住下界。

知道这个指标还有个实用副产品:看一眼开局几步开出的零区数量,就能预估这盘的 3BV 走向,决定这局值不值得认真打。

胜率的天花板

专家级(30×16、99 雷)即使每一步都按方法打,胜率也有天花板:部分盘面在推理耗尽后仍包含强制赌局。

比如终局两个格子 50/50,任何概率方法都救不了。

这是盘面生成方式的宿命:先随机布雷,再谈推理,就总有推理不了的局面。

想要”每盘都配得上推理”的体验,解法在生成端:无猜盘(no-guess board)。

流程是布雷后先用一个纯逻辑求解器(确定性推理加上受限的概率推理)试解,一旦被迫猜测就整盘重新布雷,直到生成一张”逻辑可解”的盘。

代价是生成耗时上升(专家盘可能要重试多次),收益是胜率的天花板从”看天”变成”看人”。剩下的失利全是自己的失误。

本站扫雷属于哪一类、首击保护做到哪一级,值得开局前确认一下:规则不同,概率的玩法和成绩的含金量都不同。

这些方法看一遍不如算一遍:开一局扫雷,在中期停十秒,把边界上每个候选格的概率心算出来再点,胜率的变化立竿见影。

扫雷里随机性是对手。第三个游戏更极端。它看起来最没规律,实际上根本没有随机。

三、点灯

点灯游戏(Lights Out)的规则一学就会:网格里每个灯有两种状态,按一格会翻转它自己和上下左右相邻的灯,目标是把全部灯熄灭。

看起来是个运气游戏,实际上它是伪装成游戏的线性代数习题。

解点灯等价于在一种只由 0 和 1 组成的世界里解线性方程组。

把”按灯”写成向量

关键观察是翻转顺序无关:同一格按两次等于没按,按三次等于按一次。

所以一个解不需要记录”按的顺序”,只需要记录每格按不按。

n×n 的盘面对应一个 n² 维的 0/1 向量 x,分量 1 表示这格要按。

按一格的效果也是向量:自己加上下左右共 5 个位置翻转为 1。

把所有格子的”效果向量”按列排好,得到一个 n²×n² 的矩阵 A。

于是求解点灯变成解方程:

Ax=bA x = b

其中 b 是”当前哪些灯亮着”的向量。

目标是把灯全灭,等价于再翻转恰好等于 b 的那些位置。

GF(2)

这里的高斯消元要在一个特殊数域上进行:GF(2),只有 0 和 1 两个元素,加法是异或(1+1=0),乘法是普通乘法。

翻转两次等于没按,正是”1+1=0”的物理体现。

在 GF(2) 上做消元和普通线性代数完全同构:选主元、行异或消元、判断秩。

差别只有一点。没有”数值误差”,所有运算精确。

这意味着可解性与解的个数都有干净的判据:b 是否落在 A 的列空间里。

追灯法

朴素枚举每格按不按是 2n22^{n^2} 种,5×5 就是 2252^{25} ≈ 3300 万。能跑但不优雅。

**追灯法(light chasing)**利用了结构:

  1. 第一行的按法完全决定一切。自顶向下逐行”追灯”:第 i 行按哪些格,唯一目的是熄灭第 i−1 行还亮着的灯(在第 i 行对应位置按下)。这样逐行推进到最后一行;
  2. 最后一行按完后如果还有灯亮,说明第一行的选择不对。

于是只需要枚举第一行的按法。n 格共 2n2^n 种,5×5 是 32 种。

对每种,追灯到末行验证是否清零。清零即得完整解。

为什么第一行能决定一切?因为第二行的按法被”熄灭第一行”唯一确定,第三行被第二行唯一确定。

整棵决策树被压成了一条链。

这个方法可以手动实践:任意给一个开局,32 种第一行组合里最多试 32 次必出答案。

把追灯写成代码

追灯法的代码形态同样紧凑,核心是”第一行枚举 + 逐行强制推导”:

for press[0] in 0 .. 2^n - 1: # 第一行的按法,位掩码枚举
for r = 1 .. n-1:
# 第 r 行的按法被强制:恰好等于"第 r-1 行还亮着的灯"
# (亮灯由初始灯态、press[r-1]、press[r-2] 逐位异或得出)
press[r] = remainingLits(r-1)
if 所有灯都灭了: 记录 press 即一个解

remainingLits 是一个纯位运算小函数:一行灯的新状态 = 初始态 XOR 本行按压 XOR 相邻行按压的影响,全是对齐位掩码的异或,没有循环、没有分支。

整个求解器,枚举 32 种首行、逐行推导、末行验证,百行以内,0.1 毫秒量级出全部解。

写它的过程比读十篇分析都有用,因为每一处”为什么”都逼你回到矩阵语言。

有些开局永远无解

追灯法可能出现”32 种第一行全都失败”的局面。这说明 b 不在列空间里,这个开局无解。

这不是玄学,是矩阵的秩决定的。

经典 5×5 盘面的公认结论(Anderson & Feil 1998 年那篇著名分析):25×25 的按键矩阵秩为 23,零空间维数为 2。

两个推论:

  • 列空间只覆盖 2²³ / 2²⁵ = 1/4 的开局。随机生成的开局,如果没做可解性保证,四分之三的概率拿到无解盘面。成熟实现都会先生成一个解再倒推开局;
  • 若开局可解,则解恰好有 2² = 4 个。零空间里的任何向量加到解上仍是解。零空间的 4 个向量里,3 个非零的对应”安静模式”(quiet pattern):按下去什么都不会变的按键组合,它们不改变灯的状态,纯粹是解的多余自由度。

零空间维数随盘面尺寸变化:并非所有 n×n 都有非平凡零空间(有的尺寸矩阵满秩,开局要么唯一解要么无解,不存在多个解)。

这就是”点灯数学”最有意思的一点:解的结构由盘面尺寸的数论性质决定。

5×5 恰好是零空间维数为 2 的那类。

用矩阵语言回头看追灯法,它是分块前代消元的物理化:给定第一行的按法,其余行的按法被逐行唯一解出。

枚举第一行等于枚举 2n2^n 个自由参数,剩下的全是确定性行变换。

工程实现也出奇地小:25×25 的矩阵按行存成 25 个整数(每行一个 25 bit 的位掩码),消元就是异或,判 b 是否落在列空间、回代求解,全部在几十行代码里完成。

变体与推广

点灯是一个”家族”,改变任何一个参数,线性代数的框架原封不动,只是矩阵变了:

  • 邻接规则:按压影响从”十字五格”改成”斜向九格”或”仅自身与右下方”,对应的 A 矩阵结构不同,秩与零空间随之改变。变体的可解性没有直觉捷径,全靠重新算秩;
  • 边界条件:标准盘面的边界”出界即无影响”。换成环面(左右相接、上下相接)后矩阵循环化,秩会变。同一个尺寸的环面盘面和普通盘面,无解盘面的比例可以完全不同;
  • 高维与异形:三维点灯、异形盘面(挖角的 L 形棋盘)照样建模,“格子编号 → 向量维度”的映射换一下即可。

框架的力量正在这里:规则怎么变,“解线性方程组”这个答案不变。

顺带一提文献里常见的”All-Ones 问题”:问”能否把全亮盘面清空”。

它就是这个方程组把 b 换成全 1 向量,数学上没有新东西。只是具体尺寸下全亮局面在不在列空间里,要单独判定,结论未必与”随机盘面的可解比例”一致。

从游戏回到课堂

回头看,点灯把线性 algebra 的几个核心概念全部具象化了:

  • 列空间 = 所有可被”按灯组合”到达的盘面集合;
  • 零空间 = 按了也没效果的组合(安静模式);
  • 秩 = 能被独立控制的自由度;
  • 解的存在性 = 当前盘面是否在列空间里。

下次遇到”把灯全灭”的谜题,不用再试运气。追灯法 32 次尝试内机械出解;如果全失败,恭喜你踩到了那四分之三里的一个无解盘面。

去换一个开局,或者反手写个 50 行的高斯消元,让矩阵替你玩完这个游戏。

四、滑块谜题

点灯教的是”怎么解”。第四个谜题教的是”怎么证明无解”。

数字华容道(15-puzzle)的玩家都遇到过灵异事件:把盘子上的数字块抠下来随机装回去,然后永远拼不回去了。

这不是手残,是数学。随机重排的 15-puzzle 恰好有一半开局无解。

判定它的是一个非常优雅的不变量(置换的奇偶性),而滑块谜题的另一大分支华容道(Klotski)却完全不受这个限制。

对应练习:数字版是数字华容道,经典版是华容道。

一半的开局注定无解

15-puzzle:4×4 盘面,15 个数字块加一个空格,滑动数字进空格,目标排列是 1 到 15 依次排好、空格在右下。

关键观察:任何一次滑动都不改变”排列奇偶性 + 空格位置”联合构成的那个量。

拆开说:

  • 空格左右移:某个数字块水平挪一格。把数字块序列读成一维,这个块跨过了空格(不跨过任何数字块),序列中数字间的相对顺序不变。逆序数不变;
  • 空格上下移:某个数字块在序列里跳过了恰好 3 个数字(4 列宽的盘子,跨过同行/列的 3 个块),逆序数变化 3 次,奇偶性翻转。同时空格的行号(从底部数)变化 1,奇偶性也翻转。

于是定义量 S = 逆序数的奇偶 ⊕ 空格行号的奇偶(从底部数)。

两种滑动都让两个分量同时翻转或同时不动。

S 永远不变。

目标状态的 S 是固定的(逆序数为 0,空格在底行),凡 S 与目标不同的开局,无论怎么滑都到不了目标。

数字块抠下来随机装回,逆序数奇偶各半概率。

恰好 50% 的重排无解。

这也是为什么正经的数字华容道从不”随机洗牌”再装回,而是从终局出发倒着随机滑几千步。滑出来的必然可解。

顺带一提,这个判据对宽度为偶数的盘面用”逆序数 + 空格行号”,宽度为奇数时更简单:只看逆序数奇偶(行号变化不再影响,因为上下移动跨过的块数是偶数)。

3×3 的 8-puzzle 就属于这类。

另一个视角

同一件事还能换个更”代数”的读法,两行就完:把空格也当一块牌,每次滑动就是空格与一个数字块的换位。

换位是奇置换,所以任何一次滑动都让”15 块数字的排列奇偶”翻转一次。同时空格的行号变化 1(从底部数),行号奇偶也翻转一次。

两个量同涨同跌,它们的异或自然恒定。

这个读法把”跨过 3 个块”的算术细节全藏进”换位”一词里,推广到任何尺寸、任何形状的滑块谜题都成立。

只要数得清”从起点到终点,排列奇偶变了几次、空格位置变了几次”,可解性立判。

两种推导摆在一起,不变量的”不变”从两条路各自走到同一个量。这也是不变量方法可靠性的一个小演示。

手工判定一个开局

拿到一个开局,三十秒内判定可解,流程固定:

  1. 读序列:从左到右、从上到下读出所有数字块(跳过空格);
  2. 数逆序对:序列里”前面比后面大”的对数。不用全数。从左往右扫,对每个数数它右边有几个比它小的,累加即可;
  3. 套判据:宽度为奇数(3×3、5×5),逆序数为偶则可解、为奇则无解。宽度为偶数(4×4),算”逆序数奇偶 ⊕ 空格所在行(从底部数)的奇偶”,与目标状态的同名量比较,相同则可解。

拿经典的 8-puzzle 反例演示:

1 2 3
4 5 6
8 7 _

序列是 1 2 3 4 5 6 8 7,逆序对只有 (8, 7) 一组。逆序数为奇,判无解。

它就是终局交换了 7 和 8 两块:一次换位,奇偶翻转,永远回不去。

这个例子也解释了”抠下来重装”为什么会装出无解盘:重装就是任意重排,恰好一半概率落在目标的奇偶类之外。

华容道为什么没有这个问题

经典华容道(Klotski,4×5 盘面)看起来是近亲:滑块进空格、目标把曹操移到出口。

但它绕过了奇偶诅咒,原因有二:

  1. 块大小不一。2×2 的曹操、1×2 的横将竖将、1×1 的兵。这不是 15 个等价元素的置换问题,“逆序数”根本无从定义;
  2. 终局条件开放。曹操只要求到达出口位置,其余块的姿态无关紧要。目标是个”存在性”条件而非唯一排列。

所以华容道的每一个开局(只要空格数足够滑动)都值得搜索。不存在”先天无解”的判定,只存在”路径多长”的问题。

最短解怎么找

滑块谜题天然是图搜索问题:每个盘面格局是一个节点,一次合法滑动是边,求起点到终点的最短路径。

所有边权相等(每步代价 1),这正好是 BFS 的主场:

  • 从初始格局出发逐层扩展,第一次到达终局格局时,层数就是最少步数;
  • 关键工程细节是去重:同一个格局可以从不同路径到达,不去重会让搜索爆炸。把盘面序列化成字符串(或位编码)放进哈希表,代价 O(1) 查重;
  • 经典布局”横刀立马”(曹操居中、四将环绕、四兵在角)的公认结论是最少 81 步。要对齐口径:“一步”按一个滑块移动一次计,一次可以滑多格。据记载最早的 81 步解法发表于 1964 年马丁·加德纳在《科学美国人》的专栏,后来经计算机穷举确认没有更短的。若换算成”每滑一格算一步”,同一布局的最优路径要一百多步。比较步数前先对齐口径,两种计法的数字差着百分之几十;
  • 状态空间有多大?华容道 4×5 各块布局的组合数远小于 15!,实际可达格局在几万到几十万量级(取决于布局),现代手机一秒内可以搜完。15-puzzle 的全空间是 16!/2 ≈ 10¹³(除以 2 是因为恰有一半排列因奇偶不可达,就是上文那个判据),全空间 BFS 不是不能算,是内存装不下。每个格局哪怕只存几个字节,10¹³ 个也要以 TB 计。所以实战用 IDA*:深度优先的框架下内存只与解长度成正比,配合曼哈顿距离做启发式(机制的完整展开见图搜索篇)。

从 BFS 到 A*

BFS 数最短路的前提是边权全等,滑块谜题恰好满足(每步代价 1)。

但状态空间一大,“逐层全扩”就吃不消了。

A* 的思路是给每个格局估一个”离终点还有多远”的下界 h,优先扩展”已走步数 + h”最小的分支。只要 h 不高估(可采纳性),第一次到达终点的路径仍然是最短路径。

曼哈顿距离(每个数字块到目标位置的横纵距离之和)是天然的下界:一次滑动只能让一个块的曼哈顿距离减 1,所以真实剩余步数必然不小于当前格局的曼哈顿距离。

用高估的 h 才有错过最优解的风险。

IDA* 再把 A* 的开放列表换成迭代加深,内存从”存下已访问的全集”压到”只存当前路径”。

机制的完整细节见图搜索篇,本站的路径搜索可视化可以直接看 Dijkstra 和 A* 扩展节点的差异。

曼哈顿距离还有现成的增强版:线性冲突(linear conflict)。

同一行里有两个块,它们的目标位置也都在这一行,但先后顺序颠倒。那不管怎么走,其中一块必须先让出这一行再回来,额外多出至少两步。

把这类冲突检测出来加到启发式上(每对冲突 +2),h 依然不高估,但剪枝力显著增强。

启发式的进化路线就是这条:从”每个块各自还差多远”到”块与块之间还挤着多少别扭”。越贴近问题的真实结构,搜索越省。

另一件值得知道的武器是双向 BFS:从初始格局和终局格局各搜一半,在中间会合。

深度从 d 减半到 d/2,搜索量按分支因子的幂次算是从 bdb^d 降到约 2⋅bd/22 \cdot b^{d/2}。

华容道这类分支因子不大的空间里,省的常数就很可观。分支因子越大,这笔账越惊人。

去重结构两侧各留一份,会合时把两段路径拼起来。

工程细节只有一个坑:两侧的”边”方向相反(一侧是”怎么走到”,一侧是”从哪走回”),拼路径时别把方向搞反。

在本站的数字华容道和华容道上,你可以分别体验”判据可解的开局”和”传统布局”。

前者永远有解,后者值得数数自己的步数离 81 有多远。

离终局最远能有多远

“挑距离最大的留下”这个思路可以推到极致:可解局面里,最远的那个离终局多少步?

这等于问”状态空间图的直径(对可达半空间而言)“,答案靠全空间穷举数出来:8-puzzle 的最难关局需要 31 步,15-puzzle 的最难关局需要 80 步。

都是精确值,不是估计。

80 步意味着:任给你一个可解的 4×4 盘面,八十一格内必达终局。

任何号称”最短 120 步”的 15-puzzle 题目,要么解法不是最短,要么根本不是 15-puzzle。

这组数字还有两个实用价值。

其一,它是 IDA* 的天然验收标准:写完求解器,喂最远的那个开局,看它多少毫秒出 80。启发式的强弱在这一个测试上拉开悬殊差距。

其二,它给”难度”一个精确定义:一道题的难度 = 它到终局的最短步数。

这与数独”按技巧分级”异曲同工。好的难度标定永远锚定在求解所需的资源上,而不是表面的”块有多乱”。

随机滑动能走出多远

“从终局出发倒着随机滑几千步”这个开局生成法,还有一个值得知道的性质:难度和滑动步数直接挂钩。

滑 n 步得到的局面,每块离它终局位置的曼哈顿距离之和不超过 n。

滑 30 步的开局,最大的”总距离”就是 30,一眼就能看出不会太难。

想让开局足够乱,要么滑得多(几百步之后,局面分布已经相当分散,继续加步数的边际收益变小),要么换个思路:批量随机滑出候选局面,用 BFS 算出每个到终局的实际最短距离,挑距离最大的留下。

同样的生成成本,难度显著更稳。

这也是为什么正经的滑块游戏”随机难度”波动大:生成器有没有做这道筛选,玩家手感一摸便知。

四个游戏摆在一起,共同点是都把”凭感觉”翻译成了”可计算”。

四件工具里,不变量值得再单独看一眼,因为它是”证明做不到”的通用武器。

15-puzzle 的不可解性不靠穷举所有走法,而是找到一个”怎么走都不会变”的量,说明起点和终点在这个量上取值不同。

同一套思路在数学里处处可见。棋盘多米诺覆盖缺角问题(黑白染色计数)、扑克洗牌的循环长度(置换阶)……遇到”好像怎么都不行”的问题,先问一句:

有没有一个每次操作都保持不变的量?

如果有,算算它,答案可能一行就出来。

四件工具的共同元方法:先找到一个”游戏规则里隐藏的量”(链的完整性 / 配置的约束 / 矩阵的秩 / 排列的奇偶),然后所有的决策都对它负责。


感谢您的阅读!如果可以,给俺点些关注吧~

棋盘与逻辑谜题

周六 1月 17 2026
41222 字 · 143 分钟