B4006 [GESP202406 四级] 宝箱

适合 10-12 岁学生的互动讲解版:先学会排序 + 枚举,再认识双指针是后续优化。

一、先回答问题

结论

这道题里,你给的做法更适合当课堂主线。因为 数据范围只有 n ≤ 1000,所以 O(n²) 完全能过,而且思路更直观。

教学安排

主线先讲“排序后固定左端点,再往右试”。学生真的听懂以后,再补一句“双指针可以更快”,节奏会更稳。

二、核心观察

为什么排序

排序后,价值接近的宝箱会排在一起。这样我们更容易判断:哪些宝箱能一起拿。

小的在左边 大的在右边 合法答案会变成连续一段
关键结论

排序后,如果一段区间两端满足 a[j] - a[i] ≤ k,那么中间所有数也都合法。又因为每个价值都是正整数,所以中间的宝箱当然也应该一起拿走。

所以这题等价于:找一个连续区间,让区间最大值减最小值不超过 k,并且区间和最大。

三、互动演示

样例

样例输入:5 11 2 3 1 2。排序后变成 1 1 2 2 3

点击下面的按钮,一步一步看程序怎么做。

当前左端点 i
1
当前检查的 j
1
当前最好答案
0

准备开始:先固定左端点,再往右尝试。

当前判断:a[j] - a[i] ≤ k ?

当前区间和:0

点击“下一步”,开始模拟。

四、自己选答案

问题 1:如果已经排好序,而且发现 a[j] - a[i] > k,后面的数还要不要继续看?
问题 2:这题里 n ≤ 1000O(n²) 能不能过?

五、手推结果

左端点 能选的区间 区间和
i = 11 1 2 26
i = 21 2 25
i = 32 2 37
i = 42 35
i = 533

最后答案是 7。 也就是拿走 2, 2, 3

六、C++14 参考代码

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
int a[N];

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

    int n, k;
    cin >> n >> k;

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

    sort(a + 1, a + n + 1);

    int ans = 0;

    for (int i = 1; i <= n; i++) {
        int sum = 0;

        for (int j = i; j <= n; j++) {
            if (a[j] - a[i] <= k) {
                sum += a[j];
            } else {
                break;
            }
        }

        ans = max(ans, sum);
    }

    cout << ans << '\n';
    return 0;
}
为什么这份代码适合先讲?

因为它每一步都看得见:排序、固定左端点、往右试、累加总和、更新答案。学生可以直接对着样例手推代码。

那双指针什么时候再讲?

当学生已经熟悉“连续区间”和“越往后越大”这两个事实后,再讲双指针会很顺。否则他们容易只记代码,不理解为什么这样移动指针。

七、进阶版:双指针解法

升级思路

前面的枚举法,每换一个左端点,都要重新往右试一遍。双指针会更省事:左右指针都只往右走,不回头。

课堂顺序还是不变:先学会枚举,再认识优化。

核心想法

排序后,用一个窗口 [L, R] 表示当前选中的连续区间。

  • 先让 R 往右走,把新宝箱加进来。
  • 如果发现 a[R] - a[L] > k,说明区间不合法。
  • 这时就把 L 往右挪,同时把左边移出的值从总和里减掉。
  • 只要窗口重新合法,就可以更新答案。
要维护的量
L:窗口左端点 R:窗口右端点 sum:当前窗口总和 ans:最好答案
为什么更快

每个位置最多只会被 R 扫到一次,也最多只会被 L 移出一次,所以扫描部分是线性的。

双指针代码
#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
int a[N];

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

    int n, k;
    cin >> n >> k;

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

    sort(a + 1, a + n + 1);

    int ans = 0;
    int sum = 0;
    int L = 1;

    for (int R = 1; R <= n; R++) {
        sum += a[R];

        while (a[R] - a[L] > k) {
            sum -= a[L];
            L++;
        }

        ans = max(ans, sum);
    }

    cout << ans << '\n';
    return 0;
}
三句话读懂代码

第一句:R 每次右移,把新宝箱加入窗口。

第二句:如果区间不合法,就移动 L,直到重新合法。

第三句:每次窗口合法时,用 sum 更新 ans

两种做法对比
做法 思路难度 复杂度 建议
排序 + 枚举 更直观 O(n²) 第一次讲这题时用
排序 + 双指针 稍抽象 O(n log n) 总体
O(n) 扫描部分
当作后半段优化