博弈问题

记录leetcode中的博弈问题。

对于先手是否能赢的问题,等价问题就是:是否存在一种情况,先手选择了某个数,后手就不能赢,如果存在这种情况,先手就一定能赢。

目录

1. 292. Nim 游戏

2. 1025. 除数博弈

3. 464. 我能赢吗

4. 486. 预测赢家

4.1 解法1

 4.2 更一般的做法

5. 1140. 石子游戏 II

6.1406. 石子游戏 III

7. 1510. 石子游戏 IV

 

1. 292. Nim 游戏

你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。

你们是聪明人,每一步都是最优解。 编写一个函数,来判断你是否可以在给定石头数量的情况下赢得游戏。

示例:

输入: 4
输出: false 
解释: 如果堆中有 4 块石头,那么你永远不会赢得比赛;
     因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走。

 定义dp[i]是当前有i块石头,先手是否能赢,最后返回的就是dp[n]。先手在这一轮可以取走1~3块石头,所以dp[1]=dp[2]=dp[3] = true。那么,只要存在 j (1 <= j <= 3),使得 dp[i - j] = false,也就是后手不能赢,那么先手就能赢。

dp[i] = !dp[i-1] \,||\, !dp[i-2]\,||\,!dp[i-3]

class Solution {
public:
    bool canWinNim(int n) {
        if(n <= 3) return true;
        vector<bool> dp(n+1, false);
        // dp[i]: i块石头, 先手是否能赢
        dp[1] = dp[2] = dp[3] = true;
        for (int i = 4; i <= n; ++i)
        {
            dp[i] = !dp[i-1] || !dp[i-2] || !dp[i-3]);
        }
        return dp[n];
    }
};

不过这题测试点n非常大,这样会超时,这个问题是Bash博弈问题,结论是如果 一次能取 1 ~ m块,n % (m + 1) > 0,那么先手一定能赢,否则先手一定会输。所以这题 return n % 4 > 0 即可。

具体的做法是:首先,如果只有 m + 1块,那么最后一块肯定会被后手拿到。所以先手必赢的话,就要给后手留下 m + 1块。

如果 n % (m + 1) > 0,那么记 n = k * (m + 1) + r, (1 <= r <= m)。A先拿走r个,剩下 k * (m + 1)个,由于B只能拿1~m个,因此无论B拿多少个(记为b),A只要拿走 (m + 1 - b)个,就可以使得留给B的仍然是 (m + 1)的倍数,所以最后一块一定能被 A拿到。

如果 n % (m + 1) == 0,那么B只要采取上面的策略,使得每一次留给A的都是 m+1 的倍数,最后一块一定能被B拿到。

2. 1025. 除数博弈

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

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

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

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

 

示例 1:

输入:2
输出:true
解释:爱丽丝选择 1,鲍勃无法进行操作。
示例 2:

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

提示:

1 <= N <= 1000

显然能看出来的是如果能给对手剩下1则必胜,所有的状态应该从1开始推导。定义dp[i]:先手面对的数字是i时能否获得胜利,那么应当考察对于i的所有因子j,给对手留下i-j时对手能否获得胜利。转移方程:

 dp[i] = dp[i] \: ||\: !dp[i-j]\: (i\%j=0, j < i)

class Solution {
public:
    bool divisorGame(int N) {
        bool dp[1001] = {0};
        //dp[i]: 先手面对的数字是i能否获得胜利
        for(int i = 2; i <= N; i++)
        {
            for(int j = 1; j < i; j++)
            {
                if(i % j == 0) dp[i] = dp[i] || !(dp[i-j]);
            }
        }
        return dp[N];
    }
};

事实上,这一题可以通过数学的方式来思考。只要能够拿到2,那么必胜。如果先手面对的是一个奇数,因为奇数的所有因子都是奇数,那么他会还给对手N-x是个偶数, 这时候只要后手还给他N-x-1是个奇数,那么会重复这个过程,直到后手拿到最小的偶数2,获胜。所以,先手面对奇数则必输。反之,先手面对偶数必胜,只要-1给对手奇数即可。

class Solution {
public:
    bool divisorGame(int N) {
        return (N & 1) == 0;
    }
};

3. 464. 我能赢吗

在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到 100 的玩家,即为胜者。

如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?

例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。

给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?

你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。

示例:

输入:
maxChoosableInteger = 10
desiredTotal = 11

输出:
false

解释:
无论第一个玩家选择哪个整数,他都会失败。
第一个玩家可以选择从 1 到 10 的整数。
如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。
第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利.
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。

主要考察的是搜索。对于

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值