问题描述
有n个硬币,围成一个圆,两人比赛,每人每次可拿走一个或两个相邻的硬币,最后一个拿硬币的人胜出。现在给出初始硬币的数量,问先取的人会获胜,还是后取的人获胜。
关键/核心
对称博弈!
无论怎样取,先手肯定期望自己胜,而后手也期望自己胜,
没有人期望自己败!对每一步决策,各方都是为了自己胜利!
分析问题
当n=1时,先手胜
当n=2时,先手胜
当n=3时,先手败
当n=4时,先手胜
当n=5时,…
发现本质
假设硬币数n>3,当先手取1个或2个硬币时,将剩下n-1个或n-2个硬币,
此时后手取硬币相当于“先手”角色,由于n-1或n-2是确定的数,
此时的“先手”(实际是后手)的胜负将可以确定。
如果你自己推一遍上文“分析问题”的过程,要理解本质很容易
思路
数学归纳法、递归算法、决策树、模拟
解法一:递归算法
#include <iostream>
bool ifWin(int n);
int main()
{
using namespace std;
int n;
cin >> n;
cout << ifWin(n);
return 0;
}
bool ifMe = 0; // 记录每次递归时,取硬币者。递归时,当先手取硬币时,ifMe值为1,否则为0
// 参数n为硬币数,返回1为先手胜
bool ifWin(int n)
{
ifMe = !ifMe; // 取硬币者切换
if (n == 1) return 1; // 当n=1时,先手胜
if (n == 2) return 1; // 当n=2时,先手胜
if (n == 3) return 0; // 当n=3时,先手败
// 当n>3时
if ((ifWin(n-1)&&ifMe) || (ifWin(n-2)&&ifMe) || // 当先手所有的决策中至少有一个可以胜利时,则先手胜利是充分的
((ifWin(n-1)==0)&&(ifMe==0)) || ((ifWin(n-2)==0)&&(ifMe==0))) // 实际上是在判断:是否存在一种胜的决策并且胜者为先手,是否存在一种败的决策并且败者为后手
{
ifMe = 0; // 取硬币者切换为默认值
return 1;
}
else // 当先手所有的决策都不能胜利时,则先手失败是必然的
{
ifMe = 0; // 取硬币者切换为默认值
return 0;
}
}
上面的算法是将整个过程模拟了一遍,用到了递归算法,当n大了,速度会很慢,oj可能会报TLE。(最烦TLE,比WA还烦人)
解法二:懒人做法
这种方法不用递归算法,不存在“当n太大时,速度慢”的问题,理解起来也容易。
注意看:
当n=1时,先手胜
当n=2时,先手胜
当n=3时,先手败
当n=4时,先手胜
当n=5时,先手胜
当n=6时,先手败
当n=7时,先手胜
当n=8时,先手胜
当n=9时,先手败
当n=10时,先手胜
当n=11时,先手胜
当n=12时,先手败
…
其实列出一些连续的结果后,不难归纳出(不严谨的、非数学的,只是简单地发现规律):当n为3的倍数时,先手败,当n不是3的倍数时,先手胜
所以为了不报TLE,下面是“懒人”做法:
#include <iostream>
int main()
{
using namespace std;
int n;
cin >> n;
cout << bool(n%3);
return 0;
}
吐槽
第一种解法确实有点复杂,或是说难以理解,但是当你自己动手推一遍不同的n时的结果,很容易理解的! 我当时在研究这种解法时,在递归函数ifWin()中,对n>3时,第一个if分支只写了:
if ((ifWin(n-1)&&ifMe) || (ifWin(n-2)&&ifMe)) {...}
结果测试了半天,结果总达不到预期,让人很头疼——明明就是按游戏过程模拟的啊,怎么回事???!
当我就要放弃这个绝妙的算法(不得不说,不提运行时间的话,确实很巧妙:从原理上解决此问题,我应该是全网首个哈哈哈,留念2022/12/31, 明天就是元旦了2023加油!!!愿2023没bug没烦恼,oj永远不报TLE!) 时,我突然想到,ifWin()返回0,即决策失败时,可能是后手失败啊(ifMe==0),于是我把第一个if分支改为:
if ((ifWin(n-1)&&ifMe) || (ifWin(n-2)&&ifMe) || ((ifWin(n-1)==0)&&(ifMe==0)) || ((ifWin(n-2)==0)&&(ifMe==0))) {...}
运行测试——
功夫不负有心人!
可能有人说了,既然找规律就能解决的问题,为什么要大费周折去搞一个模拟算法呢?况且n太大时速度还很慢。
我想,对于产品的解决方案,确实是这样,但是, 这个模拟算法不只是为了解决这个问题,更是为这一类问题的解决提供了思路,它很好地阐释了问题的本质,让计算机在本质上贴合实际问题、解决实际问题!
芜湖~2023新年快乐!