设n
为初始棋盘的长度,设 为初始棋盘board[i]
中的i
第-个符号(从左侧开始,从 1 开始的索引),以及board[i..j]
初始棋盘在索引i
和j
包含值之间的段。
i
如果轮到的玩家board[1..i]
可以强制获胜,我们称其为获胜长度,如果每个可能的移动都board[1..i]
为其他玩家留下获胜长度,则称为失败长度。
0 是失败的长度(根本不可能移动),1 是获胜的长度(删除唯一剩余的符号立即获胜)。
每个长度要么是赢要么是输:如果任何可能的移动为其他玩家留下了一个失败的长度,我们可以通过选择该移动来强制获胜,否则根据定义它是一个失败的长度。
我们可以找出我们是否可以强制获胜,如果是这种情况,我们可以通过记录每个长度是否失败(标记为-1
)或获胜策略将是什么(一个非负整数k
,例如从剩余棋盘的末端移除C[k]
会为对手留下损失的长度;这k
通常不是唯一的,任何都可以)。
伪代码(C-ish):
int moves[n+1];
// initialise all lengths to losing
for(i = 0; i <= n; ++i) {
moves[i] = -1;
}
moves[1] = board[i]; // win by removing the last symbol
for(i = 2; i <= n; ++i) {
if (board[i] == 0) {
// no choice, the only code ending with a 0 is C[0]
moves[i] = moves[i-1] < 0 ? 0 : -1;
} else {
k = 1;
while(C[k] is_suffix_of board[1..i]) {
if (moves[i - fib(k)] < 0) {
// found a winning move
moves[i] = k;
break;
}
++k;
}
// we either found a winning move and noted it, or leave the position as losing
}
}
moves[n]
告诉我们第一个玩家是否可以强制获胜,如果可以,如何。
与纯蛮力相比的优势在于它节省了大量的重新计算,因为除了前几个状态之外的所有状态都可以通过多种方式到达。
检查是最昂贵的操作,通过注意它是and的连接顺序C[k] is_suffix_of board[1..i]
可以有所改进,所以当你知道它是 的后缀时,你只需要检查是否是 的后缀。C[k+1]
C[k-1]
C[k]
C[k]
board[1..i]
C[k-1]
board[1..(i-fib(k))]