一、游戏规则与数学背景
斐波那契博弈(Fibonacci Nim)是组合博弈论中的经典模型,规则如下:
– 初始有 N 枚石子
– 先手玩家第一次可以取 1 到 N-1 枚石子(不能全取)
– 之后每位玩家取的石子数不能超过上一位玩家取的石子数的 2 倍
– 取走最后一颗石子的人获胜
核心定理(Zeckendorf 定理):任何正整数都可以唯一地表示为若干个不连续的斐波那契数之和。基于这一定理,可以严格证明:当且仅当 N 是斐波那契数时,先手处于必败态(P-position)。
二、Zeckendorf 分解算法
import java.util.*;
/**
* 斐波那契博弈求解器
* 基于 Zeckendorf 分解与贪心策略实现必胜决策
*/
public class FibonacciNim {
// 预计算斐波那契数列(足够覆盖 int 范围)
private static final List<Integer> FIB = new ArrayList<>();
static {
FIB.add(1); // F(1)
FIB.add(2); // F(2) = 2(博弈论中常用 1,2,3,5... 而非 1,1,2,3...)
while (true) {
int next = FIB.get(FIB.size() - 1) + FIB.get(FIB.size() - 2);
if (next < 0) break; // 溢出停止
FIB.add(next);
}
}
/**
* 获取斐波那契数列列表
*/
public static List<Integer> getFibonacciList() {
return new ArrayList<>(FIB);
}
/**
* Zeckendorf 分解:将 n 分解为不连续斐波那契数之和
* 贪心策略:每次取不超过 n 的最大斐波那契数
* @return 分解后的斐波那契数列表(从大到小)
*/
public static List<Integer> zeckendorfDecomposition(int n) {
if (n <= 0) return Collections.emptyList();
List<Integer> result = new ArrayList<>();
int remaining = n;
// 从大到小遍历斐波那契数
for (int i = FIB.size() - 1; i >= 0; i--) {
int fib = FIB.get(i);
if (fib <= remaining) {
result.add(fib);
remaining -= fib;
// 跳过相邻的斐波那契数(Zeckendorf 定理保证不连续)
i--; // 跳过下一个较小的(因为不能连续)
}
}
return result;
}
/**
* 判断 n 是否为斐波那契数
*/
public static boolean isFibonacci(int n) {
return Collections.binarySearch(FIB, n) >= 0;
}
三、必败态判定与最优策略
/**
* 判断当前状态是否为必败态(P-position)
* 当剩余石子数是斐波那契数时,当前行动方必败(在双方最优策略下)
*/
public static boolean isLosingPosition(int remaining) {
return isFibonacci(remaining);
}
/**
* 计算当前最优取子数
* 策略:
* 1. 对剩余石子做 Zeckendorf 分解
* 2. 取走分解中最小的斐波那契数
* 3. 确保取子数不超过上家取子数的 2 倍
*
* @param remaining 剩余石子数
* @param lastTaken 上家取的石子数(第一次为 0,表示无限制)
* @return 最优取子数,如果无法行动返回 -1
*/
public static int optimalMove(int remaining, int lastTaken) {
if (remaining <= 0) return -1;
// 第一次行动的特殊规则:不能全取
if (lastTaken == 0) {
if (remaining == 1) return -1; // 只剩1个且第一次,无法行动
// 对剩余-1做 Zeckendorf 分解,取最小项
List<Integer> decomp = zeckendorfDecomposition(remaining);
int take = decomp.get(decomp.size() - 1); // 最小项
// 第一次不能全取,且至少取1
if (take >= remaining) take = remaining - 1;
return Math.max(1, take);
}
// 常规回合:取子数 <= 2 * lastTaken
int maxTake = Math.min(2 * lastTaken, remaining);
// Zeckendorf 分解
List<Integer> decomp = zeckendorfDecomposition(remaining);
int smallestFib = decomp.get(decomp.size() - 1);
if (smallestFib <= maxTake) {
return smallestFib;
}
// 如果最小斐波那契数超过限制,则取 maxTake(被迫)
// 这种情况通常意味着我们已经处于不利局面
return maxTake;
}
四、完整游戏模拟引擎
/**
* 游戏状态类
*/
public static class GameState {
int remaining; // 剩余石子
int lastTaken; // 上一步取的石子数(0表示第一步)
int currentPlayer; // 1 or 2
public GameState(int n) {
this.remaining = n;
this.lastTaken = 0;
this.currentPlayer = 1;
}
public GameState copy() {
GameState gs = new GameState(this.remaining);
gs.lastTaken = this.lastTaken;
gs.currentPlayer = this.currentPlayer;
return gs;
}
}
/**
* 执行一步移动
* @return 是否成功
*/
public static boolean makeMove(GameState state, int take) {
if (take <= 0) return false;
if (take > state.remaining) return false;
if (state.lastTaken > 0 && take > 2 * state.lastTaken) return false;
if (state.lastTaken == 0 && take >= state.remaining && state.remaining > 1) return false;
state.remaining -= take;
state.lastTaken = take;
state.currentPlayer = (state.currentPlayer == 1) ? 2 : 1;
return true;
}
/**
* AI 决策:使用贪心最优策略
*/
public static int aiMove(GameState state) {
return optimalMove(state.remaining, state.lastTaken);
}
/**
* 运行一场完整的斐波那契博弈
* @param n 初始石子数
* @param aiPlayer 哪个玩家由 AI 控制(1或2),0表示双方都是 AI
*/
public static void playGame(int n, int aiPlayer) {
GameState state = new GameState(n);
Scanner scanner = new Scanner(System.in);
System.out.println("=== 斐波那契博弈开始 ===");
System.out.println("初始石子: " + n);
System.out.println("规则:首次不能全取,之后每次最多取上家2倍");
while (state.remaining > 0) {
System.out.println("
剩余石子: " + state.remaining);
System.out.println("上家取了: " + state.lastTaken);
System.out.println("当前玩家: " + state.currentPlayer);
int take;
if (aiPlayer == 0 || state.currentPlayer == aiPlayer) {
take = aiMove(state);
System.out.println("AI 决策: 取 " + take + " 枚");
} else {
System.out.print("请输入取子数: ");
take = scanner.nextInt();
}
if (!makeMove(state, take)) {
System.out.println("非法移动!");
continue;
}
}
int winner = (state.currentPlayer == 1) ? 2 : 1;
System.out.println("
游戏结束!玩家 " + winner + " 获胜!");
}
五、数学正确性验证
/**
* 验证 Zeckendorf 分解的正确性:
* 1. 分解结果之和等于原数
* 2. 分解结果中没有相邻的斐波那契数
*/
public static boolean verifyZeckendorf(int n) {
List<Integer> decomp = zeckendorfDecomposition(n);
int sum = decomp.stream().mapToInt(Integer::intValue).sum();
if (sum != n) {
System.out.println("和错误: " + sum + " != " + n);
return false;
}
// 检查不连续
for (int i = 0; i < decomp.size() - 1; i++) {
int idx1 = Collections.binarySearch(FIB, decomp.get(i));
int idx2 = Collections.binarySearch(FIB, decomp.get(i + 1));
if (Math.abs(idx1 - idx2) == 1) {
System.out.println("相邻错误: F(" + idx1 + ") 和 F(" + idx2 + ")");
return false;
}
}
return true;
}
/**
* 批量测试 Zeckendorf 分解
*/
public static void runTests() {
System.out.println("=== 测试 Zeckendorf 分解 ===");
boolean allPass = true;
for (int i = 1; i <= 1000; i++) {
if (!verifyZeckendorf(i)) {
allPass = false;
System.out.println("失败: " + i);
break;
}
}
if (allPass) {
System.out.println("1-1000 全部通过!");
}
System.out.println("
=== 示例分解 ===");
int[] samples = {10, 20, 50, 100, 1000};
for (int s : samples) {
System.out.println(s + " = " + zeckendorfDecomposition(s));
}
}
六、主程序
public static void main(String[] args) {
// 先运行测试
runTests();
// 运行 AI 对战
System.out.println("
=== AI 自我对战 ===");
playGame(30, 0);
}
}
七、算法复杂度与策略分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| Zeckendorf 分解 | O(log N) | O(log N) |
| 必败态判定 | O(log N) | O(1) |
| 最优决策 | O(log N) | O(log N) |
策略证明要点:
1. 若 N = F(k) 是斐波那契数,任何合法取子都会使剩余 N’ < F(k)。由于不能取超过 2×lastTaken,对手总能通过 Zeckendorf 分解将局面恢复为斐波那契数。
2. 若 N 不是斐波那契数,取走 Zeckendorf 分解的最小项后,剩余恰好是斐波那契数,将对手送入必败态。
斐波那契博弈完美展示了数论性质如何转化为确定性博弈策略,是组合博弈论入门的经典范例。