编程算法:寻找比赛的获胜者

8
我将参加OBI(巴西信息学奥林匹克竞赛),我正在尝试解决去年的一些练习题。但是对于这个问题,我找不到答案(我翻译了一下,所以可能会有一些错误):
巧克力比赛
卡洛斯和宝拉刚刚拿到了一袋巧克力球。由于他们会吃得太快,所以他们决定比赛:
- 他们将交替吃巧克力球(宝拉始终先开始)。 - 每次,他们只能吃掉1至M颗巧克力球,其中M由宝拉的母亲决定,以免他们噎住。 - 如果一个人在轮到他时吃了K颗巧克力球,那么接下来的人不能吃K颗巧克力球。 - 谁不能根据以上规则发挥就输了。
在下面的例子中,当M = 5且共有20颗巧克力球时,卡洛斯获胜:
Who plays        How many ate        Balls left

                                     20
Paula            5                   15
Carlos           4                   11
Paula            3                   8
Carlos           4                   4
Paula            2                   2
Carlos           1                   1

请注意,最终Carlos没能吃掉2个球获胜,因为Paula在她的最后一轮吃了2个。但是Paula无法吃掉最后一个球,因为Carlos在他的最后一轮吃了1个,所以Paula不能继续游戏并输掉比赛。
两人都非常聪明,能够最优地进行游戏。如果有一个回合序列可以确保他/她独立于对手的回合而获胜,那么他/她将执行这些序列。
任务:
你的任务是找出在两个人都采取最优策略的情况下谁会获胜。
输入:
输入只包含一个测试组,应从标准输入(通常是键盘)中读取。
输入有两个整数N(2≤N≤1000000)和M(2≤M≤1000),其中N是球的数量,M是每轮允许拿的数量。
输出:
您的程序应在标准输出中打印一行,其中包含获胜者的名称。
示例:
Input:          Output:
5 3             Paula
20 5            Carlos
5 6             Paula

我一直在尝试解决这个问题,但是我不知道如何解决。

这里可以找到C语言的解决方案: http://olimpiada.ic.unicamp.br/passadas/OBI2009/res_fase2_prog/programacao_n2/solucoes/chocolate.c.txt 但是我无法理解算法。有人在另一个网站上发布了有关这个问题的问题,但没有人回答。

你能为我解释一下算法吗?

以下是程序的预期输出: http://olimpiada.ic.unicamp.br/passadas/OBI2009/res_fase2_prog/programacao_n2/gabaritos/chocolate.zip


1
你的游戏听起来非常类似于Nim -- 也许那里玩完美游戏的算法对理解这个游戏有用? - sarnold
这可能需要发布到“理论计算机科学”论坛。 - Nathaniel Ford
你想用什么语言来解决它?当涉及到这些问题时,我不是最聪明的人,但我发现通过调试器逐步执行一个简单的案例对理解算法非常有帮助。一会儿回来 - 我要尝试编写一个解决方案(到那时我猜可能已经有人回答了)。 - Rob P.
sarnold: 我会去了解一下 Nim,看起来很有帮助。 Nathaniel: 如果这不是正确的论坛,我很抱歉;这是我的第一篇帖子。 Rob: 问题并不是解决问题本身。奥林匹克竞赛网站上有一个解决方案(用 C 编写),但我无法理解算法。 - Shibata
谢谢sarnold,维基百科上的文章真的很有帮助。巧克力比赛与此游戏完全相同:http://en.wikipedia.org/wiki/Nim#The_subtraction_game_S.281.2C2.2C....2Ck.29。我还没有测试这个解决方案,但我明天会测试它。 - Shibata
这不是Nim游戏,因为在Nim游戏中没有限制你不能像对手一样移动。它也不是特定的减法游戏。 - Antti Huima
2个回答

3
假设我们有一个布尔函数FirstPlayerWin(FPW),它接受两个参数:剩余的巧克力数量(c)和上一轮取走的巧克力数(l),即第一次移动时为0。 如果且仅当第一位玩家在这种情况下保证获胜时,该程序会返回true。
基本情况是对于任何l!= 1,FPW(0,l)= false。
否则,要计算FPW(c,l),如果对于任何x <= M,x <= c,x != l,FPW(c-x,x)为false,则FPW(c,l)为true。否则为假。这就是动态编程发挥作用的地方,因为现在将FPW的计算减少到计算较小的c值的FPW。
但是,存储此公式的条目需要N * M表条目,而您指向的解决方案仅使用2N表条目。
原因是如果FPW(c,0)为true(如果巧克力计数为c,则第一位玩家赢得任何移动),但x>0时FPW(c,x)为false,则y!= x的FPW(c,y)必须为真。这是因为如果拒绝移动x会使玩家输掉,即仅通过播放x才能赢得玩家,则在禁止y时移动x可用。因此,可以重构动态编程问题,以便除了存储完整的二维数组FPW(c,x)外,还有两个数组,一个存储值FPW(c,0),另一个存储导致第一位玩家输而不是赢得单个禁止移动,如果有的话。
如何获得引用C程序的确切文本留给读者作为练习。

是的 - 如果我能在10赢,除非最后一步是7,那么只有7是可以获胜的唯一步骤,因此我不能在10赢,除非最后一步是5 - 很好。 - mcdowella

0

我认为这是另一个伪装成动态规划的练习。游戏的状态由两个量描述:剩余球数和上一次移动中吃掉的球数。当剩余球数小于等于M时,游戏要么赢(如果剩余数量不等于前一步骤中吃掉的数量),要么输(如果相等-你不能吃完所有的球,而你的对手可以吃掉你留下的球)。

如果你已经计算出了所有球数在H以下的胜利/失败情况,并将所有可能的前一位玩家吃掉的球数写在表格中,那么你可以计算出所有球数在H+1以下的答案。H+1个球和k个上一步吃掉的球的玩家将考虑所有可能性-吃掉i个球,其中i从1到M除了非法的k值,留下一个位置,有H+1-i个球和前一步骤的i个球。他们可以使用给定H个球左右的胜负情况的表来尝试找到一个合法的k值,使他们获胜。如果他们能找到这样的值,H+1/k位置就是一个赢。如果找不到,就是一个输,所以他们可以扩展表格,覆盖多达H+1个球,依此类推。

我还没有完整地阅读所有未注释的示例代码,但它看起来可能会做类似于这样的事情-使用像递归一样的动态编程来构建表格。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接