题解:洛谷 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

【核心思想】

  1. 问题分析\(N\) 个杯子,\(K\) 个装清酒(未知哪 \(K\) 个),其余装水。高桥选若干杯子喝掉,要求无论哪 \(K\) 个是清酒,都能保证至少喝到 \(X\) 毫升清酒。求最少选几个杯子,若无法满足输出 \(-1\)。这是一个最坏情况保证 + 贪心选择问题,核心在于考虑最不利情况——清酒恰好在未选中的杯子里,通过排序和选择策略确保最坏情况下也能达标。

  2. 算法选择

    • 排序 + 前缀和 + 贪心验证:将杯子按容量升序排序,考虑最坏情况:清酒恰好是容量最小的 \(K\) 个杯子。若高桥选了 \(i\) 个杯子,最优策略是选容量最大的 \(i\) 个,这样与最小的 \(K\) 个的交集最大,从而最大化最坏情况下的清酒量。
  3. 关键步骤

    • 读入数据\(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),排序主导复杂度
    • 空间复杂度:\(O(N)\),前缀和数组
  5. 最坏情况贪心与前缀和的核心思想

    • 排序降维:升序排序后,问题转化为在有序数组上分析区间交集,简化最坏情况的构造
    • 最优选择策略:要保证最坏情况下的清酒量,应优先选容量大的杯子。因为若选小杯子,清酒恰好在未选的大杯子里时损失更大
    • 最坏情况的构造:对手(自然)会将清酒放在最不利的位置。若高桥选最大的 \(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
posted @ 2026-08-27 11:05  团爸讲算法  阅读(4)  评论(0)    收藏  举报