梦境巡查

把每段移动转化为初始能量下界,用前缀最大值和后缀最大值合并每个失效补给的影响。

OJ: shumeng

题目 ID: CSP202412B

难度:普及-

标签:前缀和前缀后缀最值模拟

日期: 2026-07-31 16:21

形式化题目

依次经过区域 0,1,,n0, 1, \dots, n:从区域 j1j-1 到区域 jj 消耗 aja_j 能量,到达区域 jj1jn1 \le j \le n)后获得补给 bjb_j。初始可从梦之源携带任意初始能量,全程能量不得降到 00 以下。

对每个区域 ii,假设该区域的补给失效,求仍能完成全程的最小初始能量 wiw_i

思路

暴力做法是对每个失效位置重新模拟一遍全程,O(n2)O(n^2)。优化思路是把每段移动改写成关于初始能量的下界约束

朴素做法:逐位置模拟

先看直接做法,对每个失效补给独立模拟一次能量变化。

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:39
 */
// brute.cpp:小数据暴力解,对每个失效位置逐段模拟能量变化,复杂度 O(n^2)。
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector<long long> a(n + 1), b(n + 1);
    for (int i = 0; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];

    // 对每个可能的失效补给位置独立模拟一遍全程
    for (int failure = 1; failure <= n; failure++) {
        long long gain = 0;
        long long answer = 0;

        // gain 表示当前已经发生的净能量变化,answer 是目前必须携带的初始能量
        for (int j = 0; j <= n; j++) {
            answer = max(answer, a[j] - gain);
            gain -= a[j];
            // 第 failure 个补给失效,跳过它,其余补给照常获得
            if (j < n && j + 1 != failure) gain += b[j + 1];
        }

        if (failure > 1) cout << ' ';
        cout << answer;
    }
    cout << '\n';

    return 0;
}

每个失效位置都扫描全程,只适合小数据验证。

建立初始能量下界

定义

base[j]=k=0jakk=1jbk,base[j] = \sum_{k=0}^{j} a_k - \sum_{k=1}^{j} b_k,

其中不存在的补给和记作 00。正常情况下,要能通过第 jj 段移动,初始能量至少为 base[j]base[j],所以正常路线的答案是所有 base[j]base[j] 的最大值。

失效补给的影响

当第 ii 个补给失效时:

  • j<ij < i,第 jj 段移动发生在失效之前,约束仍是 base[j]base[j]
  • jij \ge i,少获得了 bib_i,约束变成 base[j]+bibase[j] + b_i

因此

wi=max(max0j<ibase[j], bi+maxijnbase[j])w_i = \max\left(\max_{0 \le j < i} base[j],\ b_i + \max_{i \le j \le n} base[j]\right)。

预处理 base 的前缀最大值与后缀最大值后,每个 wiw_i 都在 O(1)O(1) 时间得到。

样例推演

下表展示第一个样例各数组的关系:

jj 0 1 2 3
aja_j 5 5 5 5
base[j]base[j] 5 10 -85 -80
前缀最大值 5 10 10 10
后缀最大值 10 10 -80 -80

例如 b2=100b_2 = 100 失效时,j<2j < 2 的最大约束是 1010j2j \ge 2 的最大约束是 100+(80)=20100 + (-80) = 20,所以答案为 2020

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:39
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100005;

int n;
long long a[MAXN], b[MAXN];
long long base[MAXN];
long long prefix_maximum[MAXN];
long long suffix_maximum[MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 0; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];

    // base[j] 是正常补给下,为了通过第 j 段移动所需的初始能量下界。
    long long total_a = 0;
    long long total_b = 0;
    for (int j = 0; j <= n; j++) {
        total_a += a[j];
        if (j >= 1) total_b += b[j];
        base[j] = total_a - total_b;
    }

    prefix_maximum[0] = base[0];
    for (int j = 1; j <= n; j++) {
        prefix_maximum[j] = max(prefix_maximum[j - 1], base[j]);
    }

    suffix_maximum[n] = base[n];
    for (int j = n - 1; j >= 0; j--) {
        suffix_maximum[j] = max(suffix_maximum[j + 1], base[j]);
    }

    for (int i = 1; i <= n; i++) {
        // 失效补给只会影响第 i 段及之后的约束,之前的约束保持不变。
        long long before_failure = prefix_maximum[i - 1];
        long long after_failure = b[i] + suffix_maximum[i];
        if (i > 1) cout << ' ';
        cout << max(before_failure, after_failure);
    }
    cout << '\n';

    return 0;
}

复杂度

  • 时间:base、前缀最大值、后缀最大值各扫描一次,O(n)O(n)
  • 空间:三个 O(n)O(n) 数组。

总结

本题核心是把“能量是否足够”的过程改写成一组关于初始能量的下界。一个补给失效后,只会让它之后的所有约束统一增加同一个值 bib_i,因此前缀最大值和后缀最大值可以快速合并影响,把 O(n2)O(n^2) 压到 O(n)O(n)

图示解析

这张图串起从移动过程到单次查询答案的主线:

text
每段消耗与补给
|- 累加得到 base[j]:通过第 j 段的初始能量下界
   |- 失效位置之前:保留 base[j]
   `- 失效位置之后:统一增加 b[i]
      `- 前缀最大值与后缀最大值合并为 w[i]

图中的分叉点是失效补给的位置。它不影响之前已经发生的移动,只会让后面的每个初始能量约束增加同一个 bib_i;这正是一次前缀/后缀最值查询能够解决全部位置的原因。