题解:AtCoder AT_awc0101_b A Single Strike of Dominoes

【题目来源】

AtCoder:B - A Single Strike of Dominoes

【题目描述】

There are \(N\) pillars lined up in a row in front of Takahashi. The durability of the \(i\)-th pillar from the left is \(A_i\).

Takahashi simultaneously applies the same force \(X\) to all pillars. Each pillar that receives the impact has its durability decreased by \(X\).

After that, a chain reaction propagates from left to right. Specifically, when a pillar collapses (its durability becomes \(0\) or less), the durability of the pillar immediately to its right decreases by an additional \(1\). If this causes the right neighbor to also collapse, the durability of the pillar immediately to its right decreases by \(1\) as well. This chain continues to the right until a pillar does not collapse. Note that if the rightmost pillar collapses, no further chain reaction occurs.

In summary, whether each pillar collapses is determined from left to right by the following rule: in addition to the direct impact \(X\), a pillar receives an additional \(1\) damage if the pillar immediately to its left has collapsed.

Find the minimum value of \(X\) required to collapse all pillars. \(X\) must be a positive integer.

高橋面前有一排 \(N\) 根柱子。从左数第 \(i\) 根柱子的耐久度为 \(A_i\)

高橋同时对所有柱子施加相同的力 \(X\)。每根受到冲击的柱子耐久度减少 \(X\)

之后,连锁反应从左向右传播。具体地,当一根柱子倒塌(耐久度变为 \(0\) 或以下)时,其右侧相邻柱子的耐久度额外减少 \(1\)。如果这导致右侧相邻柱子也倒塌,则再往右一根柱子的耐久度也减少 \(1\)。该连锁反应持续向右传播,直到某根柱子没有倒塌为止。注意,如果最右边的柱子倒塌,则不再发生进一步的连锁反应。

总之,每根柱子是否倒塌按从左到右的顺序由以下规则决定:除了直接冲击 \(X\) 之外,如果其左侧相邻柱子已经倒塌,则该柱子额外受到 \(1\) 点伤害。

求使所有柱子都倒塌所需的最小 \(X\) 值。\(X\) 必须是正整数。

【输入】

The input is given in the following format.

\(N\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)

  • The first line gives the number of pillars \(N\).
  • The second line gives \(N\) integers \(A_1, A_2, \ldots, A_N\) representing the durability of each pillar, separated by spaces.

【输出】

Print the minimum value of the force \(X\) required to collapse all pillars in a single line.

【输入样例】

4
2 3 2 4

【输出样例】

3

【核心思想】

  1. 问题分析:给定 \(N\) 根柱子的耐久度 \(A_i\),需要找到一个最小的正整数 \(X\),使得所有柱子倒塌。倒塌规则为:每根柱子先受到 \(X\) 的直接伤害,然后从左到右检查,若第 \(i\) 根柱子倒塌(耐久度 \(\leq 0\)),则第 \(i+1\) 根柱子额外受到 \(1\) 点伤害。该问题具有单调性——若 \(X\) 能使所有柱子倒塌,则任何大于 \(X\) 的值也一定能。

  2. 算法选择

    • 整数二分:利用"可行性随 \(X\) 增大而单调不减"的性质,在值域 \([1, 10^9]\) 上二分查找最小可行 \(X\)
    • 模拟验证(check函数):对于给定的 \(X\),按照题目规则模拟连锁反应,判断是否能倒塌所有柱子
  3. 关键步骤

    • 二分边界:左边界 \(l = 1\),右边界 \(r = 10^9\)\(X\) 的最大可能值)
    • 二分过程
      • 取中点 \(mid = \lfloor \frac{l + r}{2} \rfloor\)
      • 调用 check(mid) 模拟验证:
        • 所有柱子先减去 \(X\)b[i] = \max(0, A_i - X)
        • 从左到右传播连锁反应:若 \(b[i] = 0\),则 \(b[i+1] = \max(0, b[i+1] - 1)\)
        • 检查是否所有 \(b[i] = 0\)
      • check(mid) 为真:\(r = mid\)(尝试更小的 \(X\)
      • check(mid) 为假:\(l = mid + 1\)(需要更大的 \(X\)
    • 输出 \(l\)(最小可行 \(X\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log C)\),其中 \(C = 10^9\)\(X\) 的值域范围。每次 check 模拟 \(O(N)\),二分约 \(\log C\)
    • 空间复杂度:\(O(N)\),原数组 \(A[1..N]\) 和临时模拟数组 \(B[1..N]\)
  5. 整数二分的核心思想

    • 单调性利用:若 \(X\) 可行,则所有 \(X' > X\) 也可行,因此可行解集合具有单调性,适合二分
    • 验证分离:将"寻找最优解"与"验证可行性"分离,通过高效的模拟验证配合二分快速缩小搜索范围
    • 边界处理:使用 max(0, ...) 防止耐久度变为负数,避免连锁反应过度传播
    • 连锁传播顺序:必须严格从左到右依次判断,因为第 \(i\) 根是否倒塌会影响第 \(i+1\) 根的状态
    • 适用于具有单调性的最优化问题,将求解转化为"判定问题 + 二分搜索"的经典模式

【算法标签】

整数二分

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 500005;           // 最大柱子数量
int n;                          // 柱子数量
int a[N], b[N];                 // a[i]: 第i根柱子的原始耐久度, b[i]: 模拟时的临时耐久度

// 检查:当施加的力为x时,是否能使所有柱子倒塌
bool check(int x)
{
    // 复制原始耐久度到临时数组,避免修改原数组
    memcpy(b, a, sizeof(a));

    // 第一步:所有柱子受到直接冲击x
    for (int i = 1; i <= n; i++)
        b[i] = max(0, b[i] - x);

    // 第二步:连锁反应从左到右传播
    // 如果第i根柱子倒塌(耐久度为0),则第i+1根额外受到1点伤害
    for (int i = 1; i < n; i++)
    {
        if (b[i] == 0)          // 第i根柱子倒塌
            b[i + 1] = max(0, b[i + 1] - 1);    // 右侧相邻柱子额外减1(不低于0)
    }

    // 检查是否所有柱子都倒塌(耐久度都为0)
    for (int i = 1; i <= n; i++)
    {
        if (b[i] > 0)           // 存在未倒塌的柱子
            return false;
    }

    return true;                // 所有柱子都倒塌
}

int main()
{
    cin >> n;                   // 读入柱子数量

    // 读入每根柱子的耐久度
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    // 二分查找最小的X
    int l = 1, r = 1e9;         // X的范围:[1, 1e9]
    while (l < r)
    {
        int mid = (l + r) / 2;  // 取中间值

        if (check(mid))         // mid可行,尝试更小的X
            r = mid;
        else                    // mid不可行,需要更大的X
            l = mid + 1;
    }

    // 输出最小的X
    cout << l << endl;

    return 0;
}

【运行结果】

4
2 3 2 4
3
posted @ 2026-06-30 20:45  团爸讲算法  阅读(8)  评论(0)    收藏  举报