题解:洛谷 B4006 宝箱
【题目来源】
洛谷:B4006 [GESP202406 四级] 宝箱 - 洛谷
【题目描述】
小杨发现了n个宝箱,其中第i个宝箱的价值是\(a_i\)。
小杨可以选择一些宝箱放入背包并带走,但是小杨的背包比较特殊,假设小杨选择的宝箱中最大价值为x,最小价值为y,小杨需要保证x-y≤k,否则小杨的背包会损坏。
小杨想知道背包不损坏的情况下,自己能够带走宝箱的总价值最大是多少。
【输入】
第一行包含两个正整数n,k,含义如题面所示。
第二行包含n个正整数\(a_1, a_2, \dots, a_n\),代表宝箱的价值。
【输出】
输出一个整数,代表带走宝箱的最大总价值。
【输入样例】
5 1
1 2 3 1 2
【输出样例】
7
【核心思想】
-
问题分析:给定 \(n\) 个宝箱价值 \(a_i\),选择一些宝箱使得最大值与最小值之差 \(\leq k\),求总价值最大。这是一个排序 + 双指针/滑动窗口问题,核心在于排序后利用单调性,将问题转化为在差值不超过 \(k\) 的连续子数组中求最大和。
-
算法选择:
- 排序:将价值数组升序排序,使差值约束转化为区间长度约束
- 滑动窗口枚举:固定右端点,向左扩展找到满足 \(a_i - a_j \leq k\) 的最左位置,计算区间和
-
关键步骤:
- 读入数据:读取 \(n, k\) 和数组 \(a[1..n]\)
- 排序:\(sort(a+1, a+n+1)\)
- 枚举右端点(\(i\) 从 \(1\) 到 \(n\)):
- \(sum \leftarrow 0\)
- 向左扩展(\(j\) 从 \(i\) 到 \(1\)):
- 若 \(a_i - a_j \leq k\):\(sum \leftarrow sum + a_j\)
- 否则 \(break\)(由于排序,更左边的元素差值更大,不满足条件)
- \(ans \leftarrow \max(ans, sum)\)
- 输出结果:\(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n + n^2)\),排序 \(O(n \log n)\),双重循环最坏 \(O(n^2)\)
- 空间复杂度:\(O(n)\),存储数组
-
排序与滑动窗口的核心思想:
- 排序转化问题:排序后,任意子集的最大值与最小值之差等于该子集在排序后数组中的首尾元素之差。因此问题转化为:在排序后的数组中找一个连续区间,使得区间首尾差 \(\leq k\) 且区间和最大
- 连续区间的最优性:排序后,满足差值约束的最优子集一定是连续区间。因为若选取不连续的元素,中间的元素价值为正,加入后不会增大差值但会增加总和
- 单调性利用:固定右端点 \(i\) 时,左端点 \(j\) 向左移动时差值单调递增,一旦 \(a_i - a_j > k\) 即可停止,利用排序的单调性实现提前终止
- 前缀和优化空间:可用前缀和将内层循环优化为 \(O(1)\),整体复杂度降至 \(O(n \log n)\),但当前实现已足够通过
- 适用于带极差约束的子集求和、排序后转化为区间问题类双指针问题
【算法标签】
普及- #双指针
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N];
int n, k;
int main()
{
// 输入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; // 以a[i]为右端点的满足条件的子数组的和
// 向左扩展,找到所有满足a[i]-a[j]<=k的元素
for (int j = i; j >= 1; j--) {
if (a[i] - a[j] <= k) {
sum += a[j];
} else {
break; // 如果不满足条件,后面的元素更小,更不满足
}
}
// 更新最大和
ans = max(ans, sum);
}
cout << ans << endl;
return 0;
}
【运行结果】
5 1
1 2 3 1 2
7
浙公网安备 33010602011771号