题解:洛谷 B3959 做题

【题目来源】

洛谷:B3959 [GESP202403 四级] 做题 - 洛谷

【题目描述】

小杨同学为了提高自己的实力制定了做题计划,在第 天时,他必须要完成 道题,否则他就会偷懒。

小杨同学现在找到了一个题库,一共有 \(n\) 套题单,每一套题单中有一定数量的题目。但是他十分挑剔,每套题单他只会使用一次,每一天也只能使用一套题单里的题目,之后那套题单就会被弃之不用。对于每套题单,他不必完成 题单内所有的题。

那么问题来了,小杨同学最多会做题几天才偷懒呢?

【输入】

第一行,\(1\) 个数为 \(n\),表示有多少套题单。

第二行,\(n\) 个整数 \(a_1,a_2,\dots, a_n\),分别表示每套题单有多少道题。

【输出】

输出一行,小杨同学偷懒前最多做题天数。

【输入样例】

4
3 1 4 1

【输出样例】

3

【核心思想】

  1. 问题分析:给定 \(n\) 套题单,每套题单有 \(a_i\) 道题。第 \(k\) 天需要完成至少 \(k\) 道题,每套题单只能用一次,每天只能用一套题单。求最多能做多少天。这是一个贪心匹配问题,核心在于将题单按题目数量排序后,逐天匹配满足当天需求的最小题单。

  2. 算法选择

    • 排序 + 贪心匹配:将题单按题目数量升序排序,第 \(k\) 天选择剩余题单中满足 \(a_j \geq k\) 的最小题单
    • 双指针优化:维护指针 \(start\) 避免重复搜索,实现 \(O(n)\) 的匹配过程
  3. 关键步骤

    • 读入数据:读取 \(n\) 和数组 \(a[1..n]\)
    • 排序\(sort(a+1, a+n+1)\) 升序排列
    • 贪心匹配
      • \(start \leftarrow 1\)\(ans \leftarrow 0\)
      • 外层枚举天数 \(i\)\(1\)\(n\)
        • 内层从 \(start\) 开始查找第一个满足 \(a_j \geq i\) 的题单
        • 若找到:\(ans \leftarrow i\)\(start \leftarrow j + 1\)\(break\)
      • 若找不到则后续天数也无法满足,循环自然结束
    • 输出结果\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),排序主导
    • 空间复杂度:\(O(n)\),存储数组
  5. 贪心匹配与双指针的核心思想

    • 排序后的单调性:升序排序后,若 \(a_j \geq i\) 则所有 \(a_{j'} \geq a_j\) 也满足。每天选择满足条件的最小题单,保留大题单给后续天数,这是最优策略
    • 双指针的线性扫描\(start\) 指针确保每套题单最多被考虑一次,将内层查找的均摊复杂度降至 \(O(1)\),整体匹配过程为 \(O(n)\)
    • 贪心正确性证明:假设第 \(k\) 天选择了题单 \(j\)(满足 \(a_j \geq k\) 的最小值),若某天选择更大的题单,则可能导致后续天数无法满足。选择最小可行题单是最优的
    • 终止条件:当某天找不到满足 \(a_j \geq i\) 的题单时,由于后续天数要求更高(\(i+1, i+2, \ldots\)),必然也无法满足,直接结束
    • 适用于资源分配、逐日需求匹配类贪心问题

【算法标签】

普及- #贪心

【解题思路】

【代码详解】

#include <bits/stdc++.h>  // 包含所有标准库头文件
using namespace std;

long long a[1000005];  // 定义全局数组存储数据

int main() {
    int n;
    cin >> n;  // 输入数据数量

    // 读取n个数据
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    sort(a + 1, a + n + 1);  // 对数组进行升序排序

    int start = 1;  // 搜索起始位置
    int ans = 0;    // 最终答案

    // 计算最大满足天数
    for (int i = 1; i <= n; i++) {  // i表示当前检查的天数
        for (int j = start; j <= n; j++) {  // 从start开始查找
            if (i <= a[j]) {  // 找到第一个满足a[j]≥i的位置
                ans = i;      // 更新最大天数
                start = j + 1; // 下次从下一个位置开始查找
                break;        // 跳出内层循环
            }
        }
    }

    cout << ans;  // 输出结果
    return 0;
}

【运行结果】

4
3 1 4 1
3
posted @ 2026-07-17 15:34  团爸讲算法  阅读(12)  评论(0)    收藏  举报