欧几里德的游戏
这是一个非常经典的博弈论题目,通常被称为 “欧几里得博弈” (Euclid’s Game)。
它的核心解法基于我们熟悉的欧几里得算法(辗转相除法),但加入了博弈的策略成分。
下面我为你提供详细的题目解析、解题思路以及代码实现。
1. 题目核心分析
这个游戏是公平组合游戏(Impartial Game),即两人面对同一局面,规则相同,且信息完全公开。我们需要判断当前状态是必胜态(Winning Position)还是必败态(Losing Position)。
设定当前状态为
游戏的操作是从
我们可以把局面分为三种情况讨论:
情况一: 是 的倍数 ( )
如果
情况二:只能减去一次 ( )
例如
情况三:自由选择权 ( )
这是题目的核心。例如
- 直接进入下一层:减去最大倍数,把局面变成
给对手。 - 构造“必败态”给对手:只减去 (最大倍数 - 1),把局面变成
给对手。
为什么这能必胜?
如果你发现局面
简而言之,当
2. 算法流程
基于上面的分析,我们可以设计出算法:
设当前两个数为
- 如果
,游戏结束(理论上输入是正整数,不会初始为0,这步是防御性编程)。 - 必胜判断:
- 如果
(成倍数,直接拿光); - 或者
(有控制权,必赢); - 当前玩家胜利,游戏结束。
- 如果
- 状态转移:
- 如果不满足上述必胜条件(即
),玩家被迫减去 。 - 新状态变为
。 - 交换行棋权(轮到下一位),继续循环。
- 如果不满足上述必胜条件(即
3. 代码实现 (C++)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <algorithm>
using namespace std;
// 分析函数:判断当前先手是否必胜
void solve() {
long long m, n; // 使用 long long 防止溢出,虽然题目说 < 2^31,但在计算中更安全
cin >> m >> n;
// 保证 m 是较大的那个数
if (m < n) swap(m, n);
bool stan_turn = true; // 标记当前是否是 Stan 的回合
while (n != 0) { // 只要较小的数不为0,游戏继续
// 情况1 & 情况3:
// 如果 m 是 n 的倍数,或者 m 足够大(>= 2n)可以让当前玩家掌握控制权
// 那么当前玩家必胜
if (m % n == 0 || m >= 2 * n) {
break; // 当前玩家赢了,跳出循环
}
// 情况2: n < m < 2n
// 只能减去一个 n,没得选,进入下一轮,交换对手
long long temp = m - n; // 新的数
m = n; // 较大的数变成原来的 n
n = temp; // 较小的数变成余数
stan_turn = !stan_turn; // 只有在被迫操作时,才会交换回合且无法分出胜负
}
if (stan_turn) {
cout << "Stan wins" << endl;
} else {
cout << "Ollie wins" << endl;
}
}
int main() {
int c;
cin >> c;
while (c--) {
solve();
}
return 0;
}
4. 复杂度分析
- 时间复杂度:该算法本质上和欧几里得算法(求GCD)是一样的。虽然我们在
时直接判赢了,但在 时就是在做辗转相减。其复杂度是对数级别的 。对于题目中的 数据,这是瞬间完成的。 - 空间复杂度:
,只需要常数个变量。
5. 总结
这道题的关键在于识破
:类似 “听天由命”,只能被迫走一步,看下一轮结果。 :就是 “我命由我不由天”,无论后续状态好坏,我都能通过策略将其转化为对我有力的局面,从而必胜。