一个有趣的博弈或推理游戏——除数博弈(动态规划与归纳法)

除数博弈

除数博弈(Divisor Game)是我在leetcode上遇到的一个题目,它的描述如下:

Alice and Bob take turns playing a game, with Alice starting first.

Initially, there is a number N on the chalkboard.  On each player's turn, that player makes a move consisting of:

Choosing any x with 0 < x < N and N % x == 0.
Replacing the number N on the chalkboard with N - x.
Also, if a player cannot make a move, they lose the game.

Return True if and only if Alice wins the game, assuming both players play optimally.

来源:力扣(LeetCode)
链接:单击这里

 中文是:

爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。

最初,黑板上有一个数字 N 。在每个玩家的回合,玩家需要执行以下操作:

选出任一 x,满足 0 < x < N 且 N % x == 0 。
用 N - x 替换黑板上的数字 N 。
如果玩家无法执行这些操作,就会输掉游戏。 

只有在爱丽丝在游戏中取得胜利时才返回 True,否则返回 false。假设两个玩家都以最佳状态参与游戏。

 给出的示例是:

输入:2
输出:true
解释:爱丽丝选择 1,鲍勃无法进行操作。
输入:3
输出:false
解释:爱丽丝选择 1,鲍勃也选择 1,然后爱丽丝无法进行操作。

题目理解 

做题的第一步肯定是要理解题意。

首先,游戏本身规则只有两条,是很简单易懂的,关键在于如何选择一个数x使自己能够获胜

其中,题目给出了一个限定条件:假设两个玩家都以最佳状态参与游戏(play optimally)。这是一个很重要的条件,这告诉我们玩家在选择x时不是随机的(都选1 或者 选尽可能大的因数 等等),因为这么选似乎与自己的胜利没多大关系。他们是有针对性地选择一个有利于自己的数字。

从上述角度思考,可以大致得到解决题目的方向。

动态规划解法

如果假设给定任意一个整数N,我们要想取得胜利,必须找到一个数x,它既是N的因数,又使得N-x的结果是输的。

也就是说,我们只要找到了这个数x,就可以选择它,然后扔给对方N-x让他输掉。相反地,如果我们找不到,那么我们就是输家。

由于要用到以前的状态(即N-x是赢还是输),所以我们自然想到了动态规划。做法如下:

1. 动态规划的各状态为拿到该数会输还是赢,对应False和True。易知dp[1] = False。由此建立了边界条件。

2. 对于一个数N,可以先初始化其状态为False。要想求得它的真实状态,则要从1到N-1的循环中寻找它的因数( N%x==0 ? ),并判断N-x的状态是否为False,若是,这个数N的状态为True。若没有,则维持原状态False。

最后可以求得N的状态,通过判断N的状态是True还是False来给出胜负与否。

代码如下:

bool divisorGame(int N)
{
    bool dp[N + 1]; // 多开一个好看一点
    dp[1] = false;
    for (int i = 2; i <= N; i++) // 建立状态数组
    {
        dp[i] = false;
        for (int j = 1; j < i; j++)
        {
            if (i % j == 0 && dp[i - j] == false) //满足那两个条件才能获胜
            {
                dp[i] = true;
                break;
            }
        }
    }
    return dp[N]; // 返回第N个状态
}

代码的时间复杂度较高,只能击败大概20-30%的提交者。那么是否还有一些潜在规律还没有被发现?是否能够进一步优化算法呢?答案是肯定的,要想击败100%,你只需要一行代码。

归纳法

归纳法的思路跟动态规划很像,也是从第1个状态往后推,一直推到第N个,唯一不同的是,它发现了两条潜在的定律:

  • 任何奇数的因数都是奇数,不可能有偶数,因此奇数减去任意一个因数必然得到偶数
  • 任何偶数的因数可以是奇数也可以是偶数,但是我们只要取因数1,减去1就必然得到一个奇数

这两条定律有什么用呢?

考虑到边界状态1是False,那么我拿到2,我就可以减1让你输,因此2是True。

再考虑3,根据规律一,你必然扔给给对方一个偶数,偶数目前只有2,对方拿到2是必然赢的,所以你会输。

再考虑4,因为3是肯定输的,那我就故意减1,让你输,所以4是True。

再考虑5,因为处理后会给对方偶数,偶数又是必胜的,所以5是会输的。

……

由此,我们就发现了这个游戏的奥秘,你拿到奇数,你必然会输(若对方知道规律),因为你处理后只能给对方偶数,如果对方知道奇数会输,偶数会赢,必然返你一个奇数(减1必然是奇数),你无法逃出规律一的魔咒,只能等着输。反之亦然。

因为题目中说,双方都会play optimally。那么代码就很简单了:

bool divisorGame(int N)
{
    return N % 2 == 0; // 判断是否为偶数
}

不妨在生活中玩一下

当你知道归纳法的规律,你就能对一个不知道规律的对手操纵游戏的进行,让对方一直输下去……直到他也可能摸索出了规律哈哈。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值