Moorbles

USACO Silver - Feb 2024

博弈论 / 贪心 / 后缀和优化

1. 题目理解

  • 初始状态: Elsie 拥有 $N$ 个弹珠。
  • 游戏过程: 共 $M$ 回合。
  • 每回合规则:
    • Elsie 猜 "奇数 (Odd)" 或 "偶数 (Even)"。
    • Bessie 从 $K$ 个数字中选一个。
    • 惩罚机制:
      若 Bessie 选的数奇偶性与 Elsie 猜的相同 $\rightarrow$ Elsie 失去该数值的弹珠。
      不同 $\rightarrow$ Elsie 获得该数值的弹珠。

目标

$$ \text{Find lexicographically smallest sequence of moves} $$ $$ \text{(Even < Odd)} $$ $$ \text{such that } Marbles > 0 \text{ at all times.} $$

关键点: Bessie 想让 Elsie 输,所以 Bessie 会采取最优策略(对 Elsie 最不利)

我们需要在最坏情况下存活!

2. 单回合最坏情况分析

对于第 $i$ 回合,给定 $K$ 个数字,我们需要算出 Elsie 选 Even 或 Odd 的最坏结果 (Delta)

Elsie 选 Even Elsie 选 Odd 若存在奇数: 失去 max(奇数) 否则: 获得 min(偶数) 若存在偶数: 失去 max(偶数) 否则: 获得 min(奇数) v[i][0] v[i][1]

预处理 Cost 数组

我们计算两个数组:

  • $v[i][0]$: 选 Even 后弹珠数量的变化量。
  • $v[i][1]$: 选 Odd 后弹珠数量的变化量。
核心思想:
对于未来某个回合 $i$,如果我们要活下来,我们肯定会选择 $v[i][0]$ 和 $v[i][1]$ 中较大的那个方案作为该回合的“最优解”。
令 $u[i] = \max(v[i][0], v[i][1])$

3. 贪心困境与后缀优化

单纯的贪心(只看当前能不能活)是不够的。

我们需要回答:"如果我这一步走了,剩下的血量够不够扛过未来所有回合的最优解中可能出现的最坏低谷?"

Suffix Minimum (后缀最小值)

定义 $min\_psum[i]$:从第 $i$ 回合开始到结束,假设我们每一步都选最优策略 ($u[k]$),过程中累计和的最低点是多少?

$$ min\_psum[i] = \min(0, \quad u[i] + min\_psum[i+1]) $$
注意:这里的 0 代表不跌破当前基准线。如果 $u[i]$ 是负很大的数,它会拉低这个值。

可视化计算 min_psum

假设 u 数组 (每回合最佳变化) 为: $[-3, +5, -4, +2]$

i=0 -3 psum:-2
i=1 +5 psum:0
i=2 -4 psum:-4
i=3 +2 psum:0
i=4 END 0
  • i=3: $min(0, 2+0) = 0$
  • i=2: $min(0, -4+0) = -4$ (危险点)
  • i=1: $min(0, 5+(-4)) = 0$ (被+5救回来了)
  • i=0: $min(0, -3+0) = -3$? 不,是 $min(0, -3 + 1)$... 实际上逻辑是累加。

User Code Logic: $min\_psum[i] = \min(0LL, min\_psum[i+1] + u[i])$

4. 最终贪心策略

从 $i = 0$ 到 $M-1$ 遍历:

Step 1: 尝试选 Even (优先)

  • 条件 A: 当前没死? $Current + v[i][0] > 0$
  • 条件 B: 未来没死? $Current + v[i][0] + min\_psum[i+1] > 0$

如果都满足 $\rightarrow$ 选 Even,更新 Current。

Step 2: 否则尝试选 Odd

  • 检查条件 A 和 B (使用 $v[i][1]$)。

Step 3: 都不行? $\rightarrow$ 输出 -1。

5. 代码实现

使用 C++ 实现上述逻辑。

预处理 v[][] 和 u[]


// 计算每一轮选Odd或Even的最坏结果
for (int i = 0; i < m; i++) {
    bool odd = false, even = false;
    int min_odd = 1e9, min_even = 1e9;
    int max_odd = -1e9, max_even = -1e9;
    // ... 读取K个数,更新min/max ...
    
    // v[i][0]: 选Even的变化量
    // 如果有奇数,Bessie会选最大的奇数让你减分 (-max_odd)
    // 如果只有偶数,Bessie只能选最小的偶数让你加分 (+min_even)
    v[i][0] = odd ? -max_odd : min_even;
    
    // v[i][1]: 选Odd的变化量
    v[i][1] = even ? -max_even : min_odd;
    
    // u[i]: 这一轮如果我选得好,最坏情况下的最好结果
    u[i] = max(v[i][0], v[i][1]);
}
                    

计算 Suffix Min


// min_psum[i] 表示从i到m,累积和相对于i时刻的最小下降值
min_psum[m] = 0;
for (int i = m-1; i >= 0; i--)
    min_psum[i] = min(0LL, min_psum[i+1] + u[i]);
                    
如果 $min\_psum[i+1]$ 是 -5, $u[i]$ 是 +2,那么 $min\_psum[i]$ 就是 -3。
意味着即便这一轮加了2,未来还是会跌掉5,总共相对于现在跌掉3。

贪心构造答案


bool ok = true;
for (int i = 0; i < m; i++) {
    // 尝试 Even (字典序小,优先)
    // 1. 当前不死: n + v[i][0] > 0
    // 2. 未来不死: n + v[i][0] + min_psum[i+1] > 0
    if (n + v[i][0] > 0 && n + v[i][0] + min_psum[i+1] > 0) {
        ans[i] = 0; // Record Even
        n += v[i][0];
    } 
    // 尝试 Odd
    else if (n + v[i][1] > 0 && n + v[i][1] + min_psum[i+1] > 0) {
        ans[i] = 1; // Record Odd
        n += v[i][1];
    } else {
        ok = false; break;
    }
}
                    

总结

  • 博弈转化: 将对手的最优操作转化为固定的数值惩罚。
  • 预见性: 使用后缀最小值 ($Suffix Min$) 来预判未来的风险。
  • 贪心策略: 在保证生存的前提下,优先选择字典序小的操作。
Time Complexity: $O(M \cdot K + M)$