[蓝桥杯]取球游戏

取球游戏

题目描述

今盒子里有 nn 个小球,A、B 两人轮流从盒中取球,每个人都可以看到另一个人取了多少个,也可以看到盒中还剩下多少个,并且两人都很聪明,不会做出错误的判断。

我们约定:

每个人从盒子中取出的球的数目必须是:1,3,7 或者 8 个。轮到某一方取球时不能弃权!A 先取球,然后双方交替取球,直到取完。被迫拿到最后一个球的一方为负方(输方)

请编程确定出在双方都不判断失误的情况下,对于特定的初始球数,A 是否能赢?

输入描述

先是一个整数 n (n<100)n (n<100),表示接下来有 nn 个整数。

然后是 nn 个整数,每个占一行(整数< 104104),表示初始球数。

输出描述

程序则输出 nn 行,表示 A 的输赢情况(输为 0,赢为 1)。

输入输出样例

示例

输入

4
1
2
10
18

输出

0
1
1
0

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

总通过次数: 1127  |  总提交次数: 1221  |  通过率: 92.3%

难度: 困难   标签: 2012, 省赛, 博弈

算法思路:动态规划博弈分析

本题是经典的博弈论问题,核心在于分析不同球数下的必胜态(Winning State)和必败态(Losing State)。根据规则,每次可取球数限定为 {1, 3, 7, 8},​​被迫取最后一个球的一方输​​。我们采用动态规划(DP)求解:

  1. ​状态定义​​:dp[i] 表示剩余 i 个球时,​​当前行动者​​(即将取球的人)是否能获胜

    • dp[i] = 1:当前行动者可获胜
    • dp[i] = 0:当前行动者必败
  2. ​状态转移​​:

    • 若存在一种取法(取 k ∈ {1,3,7,8} 个球),使对方处于必败态(dp[i-k] = 0),则 dp[i] = 1
    • 若所有取法均使对方处于必胜态(dp[i-k] = 1),则 dp[i] = 0
  3. ​边界条件​​:

    • dp[0] = 1(无球时,上一位玩家已取最后球,当前玩家获胜)
    • dp[1] = 0(只能取1球,取后对方处于 dp[0]=1 的必胜态)

算法步骤图解

剩余球数 i | 可取球数 k | 剩余 i-k | dp[i-k] | 是否获胜
------------------------------------------
i=1      | k=1      | 0       | 1      | ❌ (dp[1]=0)
i=2      | k=1      | 1       | 0      | ✅ (dp[2]=1)
i=3      | k=1      | 2       | 1      | ❌
         | k=3      | 0       | 1      | ❌ (dp[3]=0)
i=4      | k=1      | 3       | 0      | ✅ (dp[4]=1)
i=10     | k=3      | 7       | 0      | ✅ (dp[10]=1)

C++完整代码实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    // 读取输入数据
    int n;
    cin >> n;
    vector<int> balls(n);
    int max_val = 0;
    for (int i = 0; i < n; i++) {
        cin >> balls[i];
        max_val = max(max_val, balls[i]);
    }

    // 初始化DP数组(大小设为10005防止越界)
    vector<int> dp(10005, 0);
    dp[0] = 1;  // 边界条件:无球时当前玩家获胜

    // 动态规划计算所有状态(1到max_val)
    for (int i = 1; i <= max_val; i++) {
        dp[i] = 0;  // 默认设为必败态
        // 尝试四种取球策略
        int moves[] = {1, 3, 7, 8};
        for (int k : moves) {
            if (i >= k && dp[i - k] == 0) {
                dp[i] = 1;  // 存在使对手必败的策略
                break;
            }
        }
    }

    // 输出结果
    for (int x : balls) {
        cout << dp[x] << endl;
    }
    return 0;
}

代码解析

  1. ​输入处理​​:

    • 读取测试数据量 n
    • 存储所有初始球数并计算最大值 max_val(优化DP计算范围)
  2. ​DP数组初始化​​:

    • 创建 dp[10005] 确保覆盖最大球数(10^4)
    • 设置 dp[0]=1 关键边界条件

      8

  3. ​状态转移​​:

    • 遍历球数 i 从 1 到 max_val
    • 对每个 i 尝试四种取球策略 k ∈ {1,3,7,8}
    • 若 i>=k 且 dp[i-k]==0(对手必败),则 dp[i]=1
  4. ​结果输出​​:

    • 直接查询 dp[x] 输出每个初始球数的胜负

实例验证(样例输入:1,2,10,18)

初始球数计算过程输出是否符合样例
1dp[1] = 0(唯一取法使对方必胜)0
2dp[2-1]=dp[1]=0 → 获胜1
10dp[10-3]=dp[7]=0 → 获胜1
18所有取法均使对手必胜0

关键测试点

  1. ​边界值测试​​:

    • 球数=0(非法输入,但程序返回 dp[0]=1
    • 球数=1(必败态,输出0)
    • 球数=7(必败态,dp[7]=0
    • 球数=8(必胜态,dp[8]=1
  2. ​特殊序列验证​​:

    球数: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
    DP:  1 0 1 0 1 0 1 0 1 1 1  1  1  1  1  1  0  1  0
  3. ​最大性能测试​​:

    • 输入100个球数(均接近10^4),DP计算仅需40000次迭代(<0.1s)

优化建议

  1. ​预处理优化​​:

    // 预先计算全局DP表(避免重复计算)
    vector<int> dp(10005, 0);
    dp[0] = 1;
    for (int i = 1; i <= 10000; i++) {
        for (int k : {1, 3, 7, 8}) {
            if (i >= k && !dp[i - k]) {
                dp[i] = 1;
                break;
            }
        }
    }
    // 后续直接查询dp表
    • ​优势​​:多组查询时时间复杂度降为 O(1)
  2. ​数学优化(周期性)​​:

    • 观察DP序列:[1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,0,1,0]...
    • 发现 ​​每16球一个循环周期​​(1-16, 17-32...状态相同)
    • 优化代码:
      int result[16] = {1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,0};
      for (int x : balls) {
          cout << result[(x - 1) % 16] << endl;
      }
    • ​优势​​:无需DP计算,直接数学输出

注意事项

  1. ​状态定义一致性​​:

    • dp[i] 始终表示 ​​当前行动者​​ 的胜负状态
    • 初始时A先手,故直接查询 dp[x] 即为结果
  2. ​数组越界防护​​:

    • DP数组大小设为 10005(>10^4)避免越界
    • 取球前检查 i >= k
  3. ​最优策略性质​​:

    • 双方均采用最优策略时,​​先手胜负仅由球数决定​
    • 无需模拟具体取球过程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鑫鑫向栄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值