题解:洛谷 B3959 做题
【题目来源】
洛谷:B3959 [GESP202403 四级] 做题 - 洛谷
【题目描述】
小杨同学为了提高自己的实力制定了做题计划,在第 天时,他必须要完成 道题,否则他就会偷懒。
小杨同学现在找到了一个题库,一共有 \(n\) 套题单,每一套题单中有一定数量的题目。但是他十分挑剔,每套题单他只会使用一次,每一天也只能使用一套题单里的题目,之后那套题单就会被弃之不用。对于每套题单,他不必完成 题单内所有的题。
那么问题来了,小杨同学最多会做题几天才偷懒呢?
【输入】
第一行,\(1\) 个数为 \(n\),表示有多少套题单。
第二行,\(n\) 个整数 \(a_1,a_2,\dots, a_n\),分别表示每套题单有多少道题。
【输出】
输出一行,小杨同学偷懒前最多做题天数。
【输入样例】
4
3 1 4 1
【输出样例】
3
【核心思想】
-
问题分析:给定 \(n\) 套题单,每套题单有 \(a_i\) 道题。第 \(k\) 天需要完成至少 \(k\) 道题,每套题单只能用一次,每天只能用一套题单。求最多能做多少天。这是一个贪心匹配问题,核心在于将题单按题目数量排序后,逐天匹配满足当天需求的最小题单。
-
算法选择:
- 排序 + 贪心匹配:将题单按题目数量升序排序,第 \(k\) 天选择剩余题单中满足 \(a_j \geq k\) 的最小题单
- 双指针优化:维护指针 \(start\) 避免重复搜索,实现 \(O(n)\) 的匹配过程
-
关键步骤:
- 读入数据:读取 \(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n)\),排序主导
- 空间复杂度:\(O(n)\),存储数组
-
贪心匹配与双指针的核心思想:
- 排序后的单调性:升序排序后,若 \(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
浙公网安备 33010602011771号