文章

【GESP】C++四级真题 luogu-B4006 [GESP202406 四级] 宝箱

GESP C++四级2024年6月真题。本题主要考察排序、前缀和和滑动窗口思想。暴力难度不大,滑动窗口需要点思想,整体难度⭐⭐★☆☆。本题在洛谷评定为普及-

luogu-B4006 [GESP202406 四级] 宝箱

题目要求

题目描述

小杨发现了 $n$ 个宝箱,其中第 $i$ 个宝箱的价值是 $a_i$。

小杨可以选择一些宝箱放入背包并带走,但是小杨的背包比较特殊,假设小杨选择的宝箱中最大价值为 $x$,最小价值为 $y$,小杨需要保证 $x-y\leq k$,否则小杨的背包会损坏。

小杨想知道背包不损坏的情况下,自己能够带走宝箱的总价值最大是多少。

输入格式

第一行包含两个正整数 $n,k$,含义如题面所示。

第二行包含 $n$ 个正整数 $a_1,a_2,\dots,a_n$,代表宝箱的价值。

输出格式

输出一个整数,代表带走宝箱的最大总价值。

输入输出样例 #1

输入 #1
1
2
5 1
1 2 3 1 2
输出 #1
1
7

说明/提示

【样例解释】:

在背包不损坏的情况下,小杨可以拿走两个价值为 $2$ 的宝箱和一个价值为 $3$ 的宝箱。

【数据范围】:

对于全部数据,保证有 $1\leq n\leq 1000$,$0\leq k\leq 1000$,$1\leq a_i\leq 1000$。


题目分析

本题可以采用两种思路来解决:

方法一:前缀和方法

  • 首先对所有宝箱价值进行排序,这样可以保证最大值最小值的差值是连续的
  • 预处理前缀和数组,用于快速计算任意区间的宝箱价值之和
  • 枚举所有可能的价值范围(双重循环):
    • 外层循环 i 表示当前范围的最小值
    • 内层循环 j 表示当前范围的最大值
    • 如果 val[j] - val[i] ≤ k,则使用前缀和计算该范围内的总价值
    • 由于数组已排序,一旦发现不满足条件可直接break内层循环
  • 维护并更新最大总价值

时间复杂度:

  • 排序需要 $O(n\log n)$
  • 计算前缀和需要 $O(n)$
  • 双重循环枚举价值范围需要 $O(n^2)$
  • 总体时间复杂度为 $O(n^2)$

空间复杂度:

  • 需要额外的前缀和数组,空间复杂度为 $O(n)$

方法二:滑动窗口(双指针)方法

  • 同样先对宝箱价值排序
  • 使用左右两个指针维护一个滑动窗口:
    • 右指针不断向右扩展窗口,将新的宝箱加入总和
    • 当窗口内最大值与最小值的差超过k时,左指针右移缩小窗口
    • 由于数组有序,右指针位置即为当前窗口最大值,左指针位置即为最小值
  • 在窗口滑动过程中维护最大总价值

时间复杂度:

  • 排序需要 $O(n\log n)$
  • 滑动窗口只需要遍历一次数组,$O(n)$
  • 总体时间复杂度为 $O(n\log n)$

空间复杂度:

  • 只需要存储原始数组,空间复杂度为 $O(n)$

对于本题的数据范围($n\leq1000$),两种方法都可以通过,但滑动窗口方法在处理更大规模数据时具有明显优势。


示例代码

方法一: 前缀和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <algorithm>
#include <iostream>

// 存储宝箱价值的数组
int val_arys[1005];
// 存储前缀和的数组
int pre_sum[1005];
int main() {
    // 读入宝箱数量n和最大价值差k
    int n, k;
    std::cin >> n >> k;
    // 读入每个宝箱的价值
    for (int i = 0; i < n; i++) {
        std::cin >> val_arys[i];
    }
    // 对宝箱价值进行排序,便于后续处理
    std::sort(val_arys, val_arys + n);
    // 计算第一个前缀和
    pre_sum[0] = val_arys[0];
    // 计算所有前缀和
    for (int i = 1; i < n; i++) {
        pre_sum[i] = pre_sum[i - 1] + val_arys[i];
    }
    // 记录最大总价值
    int max_sum = 0;
    // 枚举所有可能的价值范围
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            // 如果当前价值范围满足条件(最大值减最小值不超过k)
            if (val_arys[j] - val_arys[i] <= k) {
                // 使用前缀和计算当前范围内的总价值并更新最大值
                max_sum =
                    std::max(max_sum, pre_sum[j] - pre_sum[i] + val_arys[i]);
            } else {
                // 由于数组已排序,如果当前j位置与i位置的差值超过k
                // 后续的差值一定更大,可以直接跳出内层循环
                break;
            }
        }
    }
    // 输出结果
    std::cout << max_sum;
    return 0;
}

方法二: 滑动窗口(双指针)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <algorithm>
#include <iostream>

// 存储宝箱价值的数组
int val_arys[1005];
int main() {
    // 读入宝箱数量n和最大价值差k
    int n, k;
    std::cin >> n >> k;
    // 读入每个宝箱的价值
    for (int i = 0; i < n; i++) {
        std::cin >> val_arys[i];
    }
    // 对宝箱价值进行排序,便于使用滑动窗口
    std::sort(val_arys, val_arys + n);
    // 记录最大总价值
    int max_sum = 0;
    // 滑动窗口左边界
    int left = 0;
    // 当前窗口内的价值总和
    int cur_sum = 0;
    // 滑动窗口右边界向右移动
    for (int right = 0; right < n; right++) {
        // 将右边界的宝箱加入当前总和
        cur_sum += val_arys[right]; 
        // 当窗口内最大值与最小值的差超过k时
        // 不断移动左边界直到满足条件
        // 由于数组已排序,right位置的值一定是当前窗口最大值
        // left位置的值一定是当前窗口最小值
        while(val_arys[right] - val_arys[left] > k) {
            // 移除窗口最左边的值,需要从当前和中减去
            cur_sum -= val_arys[left];
            // 左边界右移一位,缩小窗口范围
            // 由于数组有序,新的left位置值一定大于之前的值
            // 这样可以逐步减小最大值和最小值的差,直到满足条件
            left++;
        }
        // 更新最大总价值
        max_sum = std::max(max_sum, cur_sum);
    }

    // 输出结果
    std::cout << max_sum;
    return 0;
}

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

GESP 学习专题站:GESP WIKI

luogu-”系列题目可在洛谷题库进行在线评测。

bcqm-”系列题目可在编程启蒙题库进行在线评测。

欢迎加入Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答

欢迎加入C++ GESP/CSP认证学习QQ频道,考试资源总结汇总

欢迎加入C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
本文由作者按照 CC BY-NC-SA 4.0 进行授权