【题目链接】
ybt 1218:取石子游戏
OpenJudge 2.5 6266:取石子游戏
【题目考点】
1. 博弈:完全信息博弈
博弈树:
博弈树的结点对应于某一个棋局,其分支表示走一步棋;根部对应于开始位置,其叶表示对弈到此结束。在叶节点对应的棋局中,竞赛的结果可以是赢、输或者和局 。
如果该博弈为完全信息博弈,那么博弈双方都清楚整个博弈树,那么该游戏在开始前即可确定先手必胜/必败/或最好情况只能是平局。
博弈树的每一个结点有三种标识:w(对应于赢)、d(对应于和局)或者l(对应于输)。
如果当前的棋局是标有w的,那么此时先手有必胜策略。如果结点标识为d,那么除非对手失误,否则先手最好的情况也只能达成和局;如果结点标识为l ,那么无论先手如何下,对手都有必胜策略。
确定各结点标识的方法如下:
首先,叶子结点标识棋局结束,可以很多容易地判定叶子结点的标识。
如果该结点的子结点都是w,那么说明自己走一步棋后,对手总有必胜策略,那本结点对应的棋局先手必输,标记为l。
如果该结点的子结点存在l,那么自己走一步棋后,棋局变为标记为l的结点,对手输,自己胜。所以本结点标记为w。
如果该结点的子结点不存在l,但存在d,那么自己走一步棋后,棋局变为标记为d的结点,可以争取到平局,本结点标记为d。
按此方法标记每个结点。
如果根结点标记为w,那么先手有必胜策略。如果根结点标记为l,那么后手有必胜策略。如果根结点标记为d,那么先手有至少达到和局的策略。
【解题思路】
1. 建立博弈树
两堆石子数目确定后,构成一种棋局,在双方都不犯错误的情况下,先手必胜或必败。
根据题意,画出测试用例对应的博弈树:
先画出剩下两堆石子个数可能的变化,再确定标记。
在(2,10)时,此时先手可以做到在10中拿2的5倍,将第2堆拿完,取得胜利,所以此处标记为w。(2,10)的父结点(10,12)自然为l。而(10,12)的父结点自然为w。
对于输入(15,24),可以得到博弈树:
进而得知先手必输。
2. 证明题目中的提示
证明:如果 ⌊ a b ⌋ < 2 \lfloor \frac{a}{b} \rfloor < 2 ⌊ba⌋<2,那么先手只有唯一的一种取法。
证明:假设石子数目为(a,b)且 a ≥ b a \ge b a≥b,如果 ⌊ a b ⌋ ≥ 2 \lfloor \frac{a}{b} \rfloor \ge 2 ⌊ba⌋≥2则先手必胜。
3. 编码
设递归函数bool dfs(int a, int b)
,意为当两堆石子分别是(a,b),且
a
≥
b
a\ge b
a≥b,时先手是否必胜。
- 如果当前两堆石子a是b的倍数,那么先手可以直接拿光a,本轮先手必胜。
-
⌊
a
b
⌋
≥
2
\lfloor \frac{a}{b} \rfloor \ge 2
⌊ba⌋≥2即整除运算
a/b >= 2
,那么本轮先手必胜。 - 否则本轮只能有一种取法,就是在a中取走b,剩下两堆石子为(b, a-b),其中
b
≥
a
−
b
b \ge a-b
b≥a−b。下一轮先手必胜的情况为
dfs(b, a-b)
,如果下一轮先手必胜,那么本轮先手必败。如果下一轮先手必败,那么本轮先手必胜。所以下一轮的先手必胜情况与本轮相反,所以本轮的先手必胜情况为!dfs(b, a-b)
【题解代码】
解法1:博弈 递归
#include <bits/stdc++.h>
using namespace std;
bool dfs(int a, int b)//a:较多一堆石子的数量 b:较少一堆石子的数量 返回:此时是否先手必胜
{
if(a%b == 0)
return true;//一堆是另一堆的整数倍,此时先手一定可以拿光一堆获得胜利。
if(a/b >= 2)//如果floor(a/b)>=2,那么先手必胜
return true;
else
return !dfs(b, a-b);//下一轮先手是否必胜的情况为dfs(b, a-b),本轮先手的获胜情况与之相反。
}
int main()
{
int a, b;
while(cin >> a >> b)
{
if(a == 0 && b == 0)
break;
if(a < b)
swap(a, b);//保证a较大,b较小
cout << (dfs(a, b) ? "win" : "lose") << endl;
}
return 0;
}