题解:洛谷 AT_abc441_c Sake or Water
【题目来源】
洛谷:AT_abc441_c [ABC441C] Sake or Water - 洛谷
【题目描述】
有 \(N\) 个杯子,每个杯子都装有一种无色透明的液体。
具体来说,第 \(i\) 个杯子 \((1\leq i\leq N)\) 中有 \(A_i\) 毫升液体。
众所周知,这些杯子中正好有 \(K\) 个装的是清酒(米酒),其余的装的是水。但是,不知道哪些杯子装的是清酒。高桥可以选择一些(一个或多个)杯子,喝掉其中的所有液体。
求无论哪些杯子装有清酒,在保证他至少喝 \(X\) 毫升清酒的前提下,他至少需要选择多少个杯子?如果无法选择,请输出 \(-1\)。
【输入】
输入内容由标准输入提供,格式如下:
\(N\) \(K\) \(X\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)
【输出】
打印高桥为满足条件而需要选择的最少杯子数。如果无法选择,则输出 \(-1\)。
【输入样例】
3 2 5
10 6 8
【输出样例】
2
【核心思想】
-
问题分析:\(N\) 个杯子,\(K\) 个装清酒(未知哪 \(K\) 个),其余装水。高桥选若干杯子喝掉,要求无论哪 \(K\) 个是清酒,都能保证至少喝到 \(X\) 毫升清酒。求最少选几个杯子,若无法满足输出 \(-1\)。这是一个最坏情况保证 + 贪心选择问题,核心在于考虑最不利情况——清酒恰好在未选中的杯子里,通过排序和选择策略确保最坏情况下也能达标。
-
算法选择:
- 排序 + 前缀和 + 贪心验证:将杯子按容量升序排序,考虑最坏情况:清酒恰好是容量最小的 \(K\) 个杯子。若高桥选了 \(i\) 个杯子,最优策略是选容量最大的 \(i\) 个,这样与最小的 \(K\) 个的交集最大,从而最大化最坏情况下的清酒量。
-
关键步骤:
- 读入数据:\(N\)、\(K\)、\(X\) 和数组 \(A[1..N]\)
- 升序排序:\(a[1] \leq a[2] \leq \cdots \leq a[N]\)
- 前缀和预处理:\(sa[i] = \sum_{j=1}^{i} a[j]\)
- 贪心验证(\(i\) 从 \(1\) 到 \(N\)):
- 最坏情况:清酒是容量最小的 \(K\) 个,即 \(a[1..K]\)
- 若选 \(i\) 个杯子,最优策略选最大的 \(i\) 个,即 \(a[N-i+1..N]\)
- 两者交集:\(a[\max(1, N-i+1)..K]\)(当 \(N-i+1 \leq K\) 时存在交集)
- 交集容量和:\(sum = sa[K] - sa[N-i]\)(取 \(a[N-i+1..K]\) 的和)
- 若 \(sum \geq X\):\(ans = i\),终止循环
- 输出结果:\(ans\)(若始终不满足则为 \(-1\))
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),排序主导复杂度
- 空间复杂度:\(O(N)\),前缀和数组
-
最坏情况贪心与前缀和的核心思想:
- 排序降维:升序排序后,问题转化为在有序数组上分析区间交集,简化最坏情况的构造
- 最优选择策略:要保证最坏情况下的清酒量,应优先选容量大的杯子。因为若选小杯子,清酒恰好在未选的大杯子里时损失更大
- 最坏情况的构造:对手(自然)会将清酒放在最不利的位置。若高桥选最大的 \(i\) 个,对手会将清酒放在最小的 \(K\) 个,使交集最小。此时交集为 \(a[N-i+1..K]\)(当 \(N-i < K\) 时)
- 单调性与提前终止:随着 \(i\) 增大,选中的集合扩大,与最小 \(K\) 个的交集增大,\(sum\) 单调不减。因此第一个满足 \(sum \geq X\) 的 \(i\) 即为最小答案
- 无解判定:若遍历完所有 \(i\) 仍无满足条件的,说明即使选所有 \(N\) 个杯子,最坏情况下也无法保证喝到 \(X\) 毫升清酒,输出 \(-1\)
- 适用于最坏情况保证、贪心策略设计、排序降维类问题
【解题思路】

【算法标签】
普及- #贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 300005;
int n, k, x;
int a[N], sa[N]; // a: 原始数组, sa: 前缀和数组
signed main()
{
// 输入
cin >> n >> k >> x;
// 读取数组
for (int i = 1; i <= n; i++)
cin >> a[i];
// 对数组排序(升序)
sort(a + 1, a + n + 1);
// 计算前缀和
for (int i = 1; i <= n; i++)
sa[i] = sa[i - 1] + a[i];
// 调试输出
// for (int i=1; i<=n; i++)
// cout << sa[i] << " ";
// cout << endl;
int ans = -1; // 初始化为-1,表示没找到
// 遍历所有可能的i
for (int i = 1; i <= n; i++) {
// 计算sum = 最大的k个元素和 - 最小的i个元素和
int sum = sa[k] - sa[n - i];
if (sum >= x) { // 如果sum大于等于x
ans = i; // 记录答案
break; // 找到就退出
}
}
cout << ans << endl;
return 0;
}
【运行结果】
3 2 5
10 6 8
2
浙公网安备 33010602011771号