J-L25 贪心
覆盖词条:KJ-31a | 先修:J-L20/L23
贪心入门 · 排序取最值
规范币制找零
规范币制找零
题目描述
售货机里有无限张 10 元、5 元、2 元、1 元纸币。顾客付款后需要找零 \(n\) 元,每张纸币都算一张。求找零所用的最少纸币张数(要求用贪心:优先使用大面额)。
输入格式
一个整数 \(n\)。
输出格式
一个整数,为最少张数。
样例输入
样例输出
(10 + 10 + 2 + 1,共 4 张。)
数据范围
⬇ 点击下载数据包
规范币制找零 AC 代码
解题思路
贪心:每种面额能用多少张就用多少张,从大到小扫一遍。这种币制下贪心一定最优——大面额总能被若干小面额替换成更多张,交换论证成立。与下一题成对:换一种币制,贪心就会失效。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// 规范币制找零:优先用大面额,兑换后 n 一定能被剩余面额凑出
int main() {
int n;
cin >> n;
int cnt = 0;
for (int v : {10, 5, 2, 1}) {
cnt += n / v;
n %= v;
}
cout << cnt << endl;
return 0;
}
|
反例币制找零
反例币制找零
题目描述
售货亭只有 7 元、5 元、1 元三种纸币(每种无数张),要找零 \(n\) 元。小 X 的收银程序按「优先用最大面额」找零。请分别输出:
- 小 X 的贪心程序会用掉的张数;
- 真正的最少张数;
- 若两者相同输出
OK,否则输出 FAIL。
输入格式
一个整数 \(n\)。
输出格式
共三行:贪心张数、最少张数、OK 或 FAIL。
样例输入
样例输出
(贪心拿 7+1+1+1 共 4 张;最优是 5+5 只需 2 张——贪心失效。)
数据范围
⬇ 点击下载数据包
反例币制找零 AC 代码
解题思路
贪心部分与上题同型;「真正最少张数」用三重循环枚举三种面额的张数(\(n \le 100\) 规模足够)。本题是贪心适用性的标本:面额 {7,5,1} 不满足「大面额可被小面额替换」的性质,贪心不再保证最优——使用贪心前必须先论证正确性。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// 反例币制找零:面额 {7,5,1},「优先大面额」的贪心不再保证最优
int main() {
int n;
cin >> n;
int g = n, gcnt = 0; // 小 X 的收银程序:贪心
for (int v : {7, 5, 1}) {
gcnt += g / v;
g %= v;
}
// 真正最少张数:三重循环枚举三种面额各用几张
int best = n + 1;
for (int a = 0; a * 7 <= n; a++)
for (int b = 0; a * 7 + b * 5 <= n; b++) {
int rest = n - a * 7 - b * 5;
best = min(best, a + b + rest);
}
cout << gcnt << "\n" << best << "\n" << (gcnt == best ? "OK" : "FAIL") << "\n";
return 0;
}
|
购买文具
购买文具
题目描述
文具店有 \(n\) 件文具,第 \(i\) 件价格 \(p_i\) 元。预算为 \(m\) 元,每件最多买一件,求最多能买多少件。
输入格式
第一行两个整数 \(n, m\);第二行 \(n\) 个整数 \(p_i\)。
输出格式
一个整数,为最多件数。
样例输入
样例输出
(最便宜的 3+4+5+8 = 20 元恰好用完预算,共 4 件。)
数据范围
- \(1 \le n \le 10^5\)
- \(1 \le p_i \le 10^4\)
- \(1 \le m \le 10^9\)
⬇ 点击下载数据包
购买文具 AC 代码
解题思路
要件数最多,就先买便宜的:升序排序后从最便宜开始累加,加不动为止。交换论证:任何方案里的贵文具换成没买的便宜文具,件数不变、花费更小——所以「最便宜的先拿」不会更差。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
long long m;
cin >> n >> m;
vector<long long> p(n);
for (auto &x : p) cin >> x;
sort(p.begin(), p.end());
long long sum = 0;
int cnt = 0;
for (auto x : p) {
if (sum + x > m) break;
sum += x;
cnt++;
}
cout << cnt << endl;
return 0;
}
|
混合牛奶
混合牛奶
题目描述
Marry 乳业每天需要采购 \(n\) 单位牛奶,有 \(m\) 位奶农,第 \(i\) 位奶农牛奶单价为 \(p_i\),一天最多卖出 \(a_i\) 单位。每位奶农的牛奶可以只买一部分(整数数量)。求买够 \(n\) 单位牛奶的最小花费。
输入格式
第一行两个整数 \(n, m\);接下来 \(m\) 行每行两个整数 \(p_i, a_i\)。
输出格式
一个整数,为最小费用。
样例输入
| 100 5
5 20
9 40
3 10
8 80
6 30
|
样例输出
数据范围
- \(0 \le n, a_i \le 2 \times 10^6\),\(0 \le m \le 5000\),\(0 \le p_i \le 1000\)
- 保证所有奶农的总产量不少于 \(n\)
混合牛奶 AC 代码
解题思路
排序贪心入门:按单价从低到高排序,便宜的先买满,不够再买下一家——「便宜的多拿」就是这里的贪心策略。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1208 混合牛奶:单价低的奶农先买满
int main() {
long long n;
int m;
cin >> n >> m;
vector<pair<long long, long long>> f(m); // (单价, 产量)
for (auto& p : f) cin >> p.first >> p.second;
sort(f.begin(), f.end());
long long cost = 0;
for (auto& p : f) {
long long take = min(n, p.second);
cost += take * p.first;
n -= take;
if (n == 0) break;
}
cout << cost << endl;
return 0;
}
|
部分背包问题
部分背包问题
题目描述
藏宝洞里有 \(N\) 堆金币,第 \(i\) 堆总重量 \(m_i\)、总价值 \(v_i\)。背包承重为 \(T\),金币可以随意分割(分割后单位价值不变)。求能装走的最大总价值。
输入格式
第一行两个整数 \(N, T\);接下来 \(N\) 行每行两个整数 \(m_i, v_i\)。
输出格式
一个实数,表示最大价值,输出两位小数。
样例输入
| 4 50
10 60
20 100
25 100
15 45
|
样例输出
数据范围
- \(1 \le N \le 100\),\(1 \le m_i, v_i \le 100\),\(1 \le T \le 1000\)
部分背包问题 AC 代码
解题思路
可分割 → 按单位价值 \(v_i / m_i\) 从高到低装,装满整个或装到背包满为止。注意输入是先重量后价值。若金币不可分割,就变成 L28 的 0/1 背包——「可分割」正是贪心能用的前提。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P2240 部分背包:金币可分割,按单位价值从高到低装
int main() {
int n;
double t;
cin >> n >> t;
vector<pair<double, double>> a(n); // (单位价值, 重量)
for (auto& p : a) {
double m, v;
cin >> m >> v;
p = {v / m, m};
}
sort(a.rbegin(), a.rend());
double ans = 0;
for (auto& p : a) {
double take = min(t, p.second);
ans += take * p.first;
t -= take;
if (t <= 0) break;
}
printf("%.2f\n", ans);
return 0;
}
|
陶陶摘苹果·升级版
陶陶摘苹果·升级版
题目描述
苹果树结了 \(n\) 个果子,陶陶有一个 \(a\) 公分的椅子,手伸直最大长度 \(b\):苹果高度 \(x_i \le a+b\) 才够得着。每摘一个苹果要花 \(y_i\) 点力气,陶陶总共有 \(s\) 点力气。求他最多能摘到多少个苹果。
输入格式
第 1 行两个整数 \(n, s\);第 2 行两个整数 \(a, b\);接下来 \(n\) 行每行两个整数 \(x_i, y_i\)。
输出格式
一个整数,为最多能摘到的苹果数。
样例输入
| 8 15
20 130
120 3
150 2
110 7
180 1
50 8
200 0
140 3
120 2
|
样例输出
数据范围
- \(n \le 5000\),\(a \le 50\),\(b \le 200\),\(s \le 1000\),\(x_i \le 280\),\(y_i \le 100\)
陶陶摘苹果·升级版 AC 代码
解题思路
两步贪心:先按高度 \(x_i \le a+b\) 过滤出够得着的苹果,再按力气 \(y_i\) 从小到大摘——「代价小的优先」保证个数最多。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1478 陶陶摘苹果升级版:够得着的苹果里,挑力气花费最小的先摘
int main() {
int n, s, a, b;
cin >> n >> s >> a >> b;
int reach = a + b;
vector<int> cost;
for (int i = 0; i < n; i++) {
int x, y;
cin >> x >> y;
if (x <= reach) cost.push_back(y);
}
sort(cost.begin(), cost.end());
int cnt = 0;
for (int c : cost) {
if (s < c) break;
s -= c;
cnt++;
}
cout << cnt << endl;
return 0;
}
|
仓库选址
仓库选址
题目描述
数轴上有 \(n\) 家商店,第 \(i\) 家在坐标 \(x_i\)。要建一个仓库(建在整数坐标上),使所有商店到仓库的距离总和最小。求这个最小总和。
输入格式
第一行一个整数 \(n\);第二行 \(n\) 个整数 \(x_i\)。
输出格式
一个整数,为最小距离总和。
样例输入
样例输出
(仓库放在 7:6 + 4 + 0 + 2 + 3 = 15。)
数据范围
- \(1 \le n \le 2000\)
- \(0 \le x_i \le 10^9\)
⬇ 点击下载数据包
仓库选址 AC 代码
解题思路
排序取中位数 \(x_{\lceil n/2 \rceil}\)。直觉:仓库每向右挪 1,左边每家店多走 1、右边每家少走 1——挪到「两边家数打平」的点就再也不亏,这个点正是中位数(\(n\) 为偶数时两个中间位置之间的任意点都最优)。距离总和最多约 \(2000 \times 10^9\),开 long long。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<long long> x(n);
for (auto &v : x) cin >> v;
sort(x.begin(), x.end());
// 中位数最优:把仓库放在中间那家店,两侧偏移互相抵消
long long best = x[(n - 1) / 2], total = 0;
for (auto v : x) total += llabs(v - best);
cout << total << endl;
return 0;
}
|
数列分段 Section I
数列分段 Section I
题目描述
给定长度为 \(N\) 的数列 \(A_i\),把它分成连续的若干段,使每段之和都不超过 \(M\)。求最少能分成多少段。
输入格式
第一行两个正整数 \(N, M\);第二行 \(N\) 个非负整数 \(A_i\)。
输出格式
一个整数,为最少段数。
样例输入
样例输出
(划分成 [4]、[2 4]、[5 1] 三段。)
数据范围
- \(1 \le N \le 10^5\),\(M \le 10^9\) 且 \(M\) 大于数列中的最大值,\(A_i\) 之和不超过 \(10^9\)
数列分段 Section I AC 代码
解题思路
顺序装箱:从左到右扫,能塞进当前段就塞,塞不下就另起一段——「能不新开段就不新开」显然最优。与 J-L22 的「数列分段 Section II」成对:那题求最大段最小化,要用二分答案;本题顺序固定,贪心一遍即可。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1181 数列分段 Section I:顺序装箱,装不下就另起一段
int main() {
int n;
long long m;
cin >> n >> m;
int cnt = 1;
long long cur = 0;
for (int i = 0; i < n; i++) {
long long x;
cin >> x;
if (cur + x <= m) cur += x;
else { cnt++; cur = x; }
}
cout << cnt << endl;
return 0;
}
|
交换论证 · 排序键设计
排队接水
排队接水
题目描述
\(n\) 个人在一个水龙头前排队接水,第 \(i\) 个人接水需要 \(T_i\) 时间。请找出一种排队顺序,使 \(n\) 个人的平均等待时间最小(等待时间不包括自己接水的时间)。若两人接水时间相同,编号小的应排前面。
输入格式
第一行一个整数 \(n\);第二行 \(n\) 个整数 \(T_i\)。
输出格式
共两行:第一行为排队顺序(编号序列);第二行为平均等待时间,精确到小数点后两位。
样例输入
| 10
56 12 1 99 1000 234 33 55 99 812
|
样例输出
| 3 2 7 8 1 4 9 6 10 5
291.90
|
数据范围
- \(1 \le n \le 1000\),\(1 \le T_i \le 10^6\)(不保证 \(T_i\) 互不相同)
排队接水 AC 代码
解题思路
交换论证的启蒙题:把相邻两人交换,总等待时间的变化只取决于这两人的接水时间——短的在前总时间更小,所以「短作业优先」最优。用 (时间, 编号) 二元组排序,天然处理同时间小编号在前。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1223 排队接水:短作业优先(交换论证可证),同时间小编号在前
int main() {
int n;
cin >> n;
vector<pair<long long, int>> t(n); // (接水时间, 编号)
for (int i = 0; i < n; i++) {
cin >> t[i].first;
t[i].second = i + 1;
}
sort(t.begin(), t.end());
double sum = 0, wait = 0;
for (int i = 0; i < n; i++) {
cout << t[i].second << " \n"[i == n - 1];
sum += wait; // 第 i 个人等前面所有人接完
wait += t[i].first;
}
cout << fixed << setprecision(2) << sum / n << "\n";
return 0;
}
|
拼最大数
拼最大数
题目描述
给定 \(n\) 个正整数,把它们全部拼接到一起(每个数用且用一次,数与数之间不加空格),使得到的数尽量大。输出这个最大的数。
输入格式
第一行一个整数 \(n\);第二行 \(n\) 个正整数。
输出格式
拼接成的最大整数(一行输出)。
样例输入
样例输出
(拼接顺序 343、312、13。)
数据范围
- \(1 \le n \le 1000\)
- \(1 \le a_i \le 10^9\)(输入不含前导零)
⬇ 点击下载数据包
拼最大数 AC 代码
解题思路
不能按数值排(3 应排在 312 前面),也不能按字典序盲排——正确的比较是拼接后比:\(a\)、\(b\) 谁前谁后,看 \(a+b\) 与 \(b+a\) 哪个大。这正是一次交换论证:相邻两数交换只影响这两数拼接处的值,局部最优推广到全局(该比较具有传递性)。把数当字符串存,自定义比较器排序后依次拼接。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<string> s(n);
for (auto &x : s) cin >> x;
// 交换论证:两个串 a、b,谁拼在前面大就谁在前——比较 a+b 与 b+a
sort(s.begin(), s.end(), [](const string &a, const string &b) {
return a + b > b + a;
});
string ans;
for (auto &x : s) ans += x;
cout << ans << endl;
return 0;
}
|
跳跳!
跳跳!
题目描述
地面高度为 0,有 \(n\) 块高度为 \(h_i\) 的石头。小跳蛙从地面出发,每块石头都要恰好踩一次,从第 \(i\) 块跳到第 \(j\) 块耗费 \((h_i - h_j)^2\) 的体力(从地面跳上第 \(i\) 块耗费 \(h_i^2\))。求耗费体力值的最大值。
输入格式
第一行一个整数 \(n\);第二行 \(n\) 个正整数 \(h_i\)。
输出格式
一个整数,为最大体力值。
样例输入
样例输出
数据范围
- \(1 \le n \le 100\),\(1 \le h_i \le 10^4\)
跳跳! AC 代码
解题思路
排序后「最高 → 最低 → 次高 → 次低」交替跳:先跳上最高的石头(从地面起跳落差最大),之后每一步都在当前极值间来回横跳,让每步落差的平方尽量大。注意 \((h_i - h_j)^2\) 最大约 \(10^8\),用 long long。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P4995 跳跳!:排序后「最高→最低→次高→次低」交替跳,每步落差最大
int main() {
int n;
cin >> n;
vector<long long> h(n);
for (auto& x : h) cin >> x;
sort(h.begin(), h.end());
long long total = 0, cur = 0; // cur 从地面 0 出发
int i = 0, j = n - 1;
bool big = true;
while (i <= j) {
long long nxt = big ? h[j--] : h[i++];
total += (nxt - cur) * (nxt - cur);
cur = nxt;
big = !big;
}
cout << total << endl;
return 0;
}
|
排座椅
排座椅
题目描述
教室坐成 \(M\) 行 \(N\) 列,有 \(D\) 对同学上课时交头接耳。要增设 \(K\) 条横向通道(隔开前后同学)和 \(L\) 条纵向通道(隔开左右同学),每条通道能隔开恰好相邻的一对同学。求一种通道方案,使被隔开的交头接耳对数最多。
输入格式
第一行五个整数 \(M, N, K, L, D\);接下来 \(D\) 行每行四个整数 \(x, y, p, q\),表示坐在 \((x,y)\) 与 \((p,q)\) 的同学在交头接耳(保证两人要么同行、要么同列且相邻)。
输出格式
共两行:第一行 \(K\) 个整数,为横向通道开在第几行之后(升序);第二行 \(L\) 个整数,为纵向通道开在第几列之后(升序)。
样例输入
| 4 5 1 2 3
4 2 4 3
2 3 3 3
2 5 2 4
|
样例输出
数据范围
- \(2 \le N, M \le 1000\),\(0 \le K < M\),\(0 \le L < N\),\(0 \le D \le 2000\)
- 数据保证存在唯一最佳方案
排座椅 AC 代码
解题思路
横纵通道互不影响,独立做两次贪心:统计每个「行间隙」「列间隙」能隔开多少对说话同学,各自取前 \(K\) / \(L\) 大的位置——「收益大的位置优先」。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1056 排座椅:横纵通道独立统计各自能隔开多少对,取前 k/l 大
int rowCnt[1005], colCnt[1005];
int main() {
int m, n, k, l, d;
cin >> m >> n >> k >> l >> d;
for (int i = 0; i < d; i++) {
int x, y, p, q;
cin >> x >> y >> p >> q;
if (x == p) colCnt[min(y, q)]++; // 同行相邻 → 在两列之间开竖通道
else rowCnt[min(x, p)]++; // 同列相邻 → 在两行之间开横通道
}
vector<pair<int, int>> rows, cols; // (隔开对数, 位置)
for (int i = 1; i < m; i++) if (rowCnt[i]) rows.push_back({rowCnt[i], i});
for (int j = 1; j < n; j++) if (colCnt[j]) cols.push_back({colCnt[j], j});
sort(rows.rbegin(), rows.rend());
sort(cols.rbegin(), cols.rend());
vector<int> ra, ca;
for (int i = 0; i < k && i < (int)rows.size(); i++) ra.push_back(rows[i].second);
for (int i = 0; i < l && i < (int)cols.size(); i++) ca.push_back(cols[i].second);
sort(ra.begin(), ra.end());
sort(ca.begin(), ca.end());
for (int i = 0; i < (int)ra.size(); i++) cout << ra[i] << " \n"[i == (int)ra.size() - 1];
if (ra.empty()) cout << "\n";
for (int i = 0; i < (int)ca.size(); i++) cout << ca[i] << " \n"[i == (int)ca.size() - 1];
if (ca.empty()) cout << "\n";
return 0;
}
|
删数问题
删数问题
题目描述
输入一个高精度正整数 \(n\)(不超过 250 位),去掉其中任意 \(k\) 个数字后,剩下的数字按原左右次序组成一个新数。求新数最小是多少。
输入格式
第一行一个高精度正整数 \(n\);第二行一个整数 \(k\)。
输出格式
一个整数,为剩下的最小数(不含前导零)。
样例输入
样例输出
数据范围
- \(n\) 的位数 \(\le 250\),\(1 \le k <\) 位数
删数问题 AC 代码
解题思路
每轮从左往右找第一个下降位(\(s_i > s_{i+1}\)),删掉 \(s_i\)——高位越小越好;若整串一直递增,删末位。重复 \(k\) 轮后去掉前导零。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1106 删数问题:每轮删掉「第一个下降位」的数字,剩 k 轮
int main() {
string s;
int k;
cin >> s >> k;
while (k--) {
int i = 0;
while (i + 1 < (int)s.size() && s[i] <= s[i + 1]) i++;
s.erase(i, 1); // 全程递增则删末位
}
while (s.size() > 1 && s[0] == '0') s.erase(0, 1); // 去前导零
if (s.empty()) s = "0";
cout << s << endl;
return 0;
}
|
保留 k 位最大数
保留 k 位最大数
题目描述
输入一个数字串 \(s\)(首位不为 0),从中保留 \(k\) 个数字、保持原有左右顺序,使剩下的 \(k\) 位数字组成的数最大。输出这个最大的 \(k\) 位数。
输入格式
第一行一个数字串 \(s\);第二行一个整数 \(k\)。
输出格式
保留 \(k\) 位后能组成的最大数(按 \(k\) 位原样输出)。
样例输入
样例输出
(相当于删去 1 和 3,保留 7 5 4 8——与「删数问题」是同一个数字串。)
数据范围
- 数字串长度 \(2 \le L \le 15\),首位不为 0
- \(1 \le k < L\)
⬇ 点击下载数据包
保留 k 位最大数 AC 代码
解题思路
与「删数问题」是同一枚硬币的两面:删去 \(L - k\) 位使剩余最大 ⟺ 保留 \(k\) 位使数最大。贪心用栈:扫描每位,当栈顶数字比当前位小、且还有删除名额时,弹掉栈顶(高位换更大的数字必然更优);扫完若名额没用完,把末尾截掉。两者都是「邻位交换只会更差」的贪心。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
string s;
int k;
cin >> s >> k;
int rem = (int)s.size() - k; // 还可以丢弃的字符数
string st; // 栈:保留的数字(保持原相对次序)
for (char c : s) {
// 栈顶比当前数字小还舍得丢吗?丢掉它能让高位变大
while (!st.empty() && rem > 0 && st.back() < c) {
st.pop_back();
rem--;
}
st.push_back(c);
}
// 若一路递增没丢够,把末尾多余的截掉
cout << st.substr(0, k) << endl;
return 0;
}
|
纪念品分组
纪念品分组
题目描述
有 \(n\) 件纪念品,价格为 \(P_i\)。要把它们分组发放,每组最多 2 件,且组内价格之和不超过 \(w\)。求最少需要分多少组。
输入格式
第一行一个整数 \(w\);第二行一个整数 \(n\);接下来 \(n\) 行每行一个整数 \(P_i\)。
输出格式
一个整数,为最少分组数。
样例输入
| 100
9
90
20
20
30
50
60
70
80
90
|
样例输出
数据范围
- \(1 \le n \le 3 \times 10^4\),\(80 \le w \le 200\),\(5 \le P_i \le w\)
纪念品分组 AC 代码
解题思路
排序 + 首尾双指针:最贵的纪念品一定要占一组,能带上最便宜的就带上(交换论证:这样不会让任何其他配对变差),带不上就独占一组。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1094 纪念品分组:排序后最贵 + 最便宜配对,配不上则最贵的独占一组
int main() {
int w, n;
cin >> w >> n;
vector<int> p(n);
for (auto& x : p) cin >> x;
sort(p.begin(), p.end());
int i = 0, j = n - 1, cnt = 0;
while (i <= j) {
if (i < j && p[i] + p[j] <= w) i++; // 最便宜的能搭最贵的就带上
j--; // 最贵的无论如何都占一组
cnt++;
}
cout << cnt << endl;
return 0;
}
|
区间贪心
线段覆盖
线段覆盖
题目描述
各大 OJ 上有 \(n\) 个比赛,第 \(i\) 个比赛的开始、结束时间分别为 \(a_i, b_i\)。参加一个比赛必须善始善终,且不能同时参加两场比赛。求最多能参加多少个比赛。
输入格式
第一行一个整数 \(n\);接下来 \(n\) 行每行两个整数 \(a_i, b_i\)。
输出格式
一个整数,为最多能参加的比赛数。
样例输入
样例输出
数据范围
- \(1 \le n \le 10^6\),\(0 \le a_i \le b_i \le 10^6\)
线段覆盖 AC 代码
解题思路
活动选择经典:按结束时间从早到晚排序,能参加就参加——早结束的先选,给后面留的机会最多(交换论证)。与下一题「区间覆盖」成对:一个求最多不重叠,一个求最少盖满,排序键一个用右端点、一个用左端点。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1803 线段覆盖:按结束时间排序,结束早的先参加(活动选择经典)
int main() {
int n;
cin >> n;
vector<pair<int, int>> v(n); // (结束时间, 开始时间)
for (auto& p : v) cin >> p.second >> p.first;
sort(v.begin(), v.end());
int cnt = 0, last = -1;
for (auto& p : v) {
if (p.second >= last) { cnt++; last = p.first; }
}
cout << cnt << endl;
return 0;
}
|
区间覆盖
区间覆盖
题目描述
数轴上有一条目标线段 \([0, L]\),另有 \(n\) 条可选线段,第 \(i\) 条为 \([a_i, b_i]\)。选最少的线段,使它们的并恰好完全覆盖 \([0, L]\)(允许重叠)。若无解输出 \(-1\)。
输入格式
第一行两个整数 \(n, L\);接下来 \(n\) 行每行两个整数 \(a_i, b_i\)。
输出格式
一个整数,为最少线段数;无解输出 \(-1\)。
样例输入
| 5 10
0 3
2 5
4 8
7 10
8 9
|
样例输出
数据范围
- \(1 \le n \le 10^5\),\(0 \le L \le 10^9\),\(0 \le a_i \le b_i \le 10^9\)
⬇ 点击下载数据包
区间覆盖 AC 代码
解题思路
按左端点排序后向右延伸:维护已覆盖到 \(cur\),每次在所有左端点 \(\le cur\) 的线段中挑右端点最远的一条(贪心:延伸得越远,用的条数越少);延伸不动且没到 \(L\) 即无解。与「线段覆盖」成对辨析排序键:按右端点求最多、按左端点求最少。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
long long L;
cin >> n >> L;
vector<pair<long long, long long>> seg(n);
for (auto& s : seg) cin >> s.first >> s.second;
sort(seg.begin(), seg.end()); // 按左端点排序
long long cur = 0; // 已覆盖 [0, cur]
int cnt = 0;
for (int i = 0; i < n && cur < L; ) {
if (seg[i].second <= cur) { i++; continue; } // 整条被盖住,跳过
if (seg[i].first > cur) break; // 与已覆盖区断档:无解
long long reach = cur; // 左端点 ≤ cur 的线段里挑右端点最远的
while (i < n && seg[i].first <= cur) {
reach = max(reach, seg[i].second);
i++;
}
cur = reach;
cnt++;
}
if (cur < L) cout << -1 << "\n";
else cout << cnt << "\n";
return 0;
}
|
射爆气球
射爆气球
题目描述
数轴上有 \(n\) 个气球,第 \(i\) 个覆盖区间 \([l_i, r_i]\)(含端点)。一支箭射中坐标 \(x\) 会引爆所有满足 \(l_i \le x \le r_i\) 的气球。求引爆全部气球的最少箭数。
输入格式
第一行一个整数 \(n\);接下来 \(n\) 行每行两个整数 \(l_i, r_i\)。
输出格式
一个整数,为最少箭数。
样例输入
样例输出
(一支箭放在 x=6 射爆 [1,6] 和 [2,8];一支放在 x=12 射爆 [7,12] 和 [10,16]。)
数据范围
- \(1 \le n \le 10^5\)
- \(0 \le l_i \le r_i \le 10^9\)
⬇ 点击下载数据包
射爆气球 AC 代码
解题思路
按右端点从小到大排序:第一支箭放在第一个区间的右端点——放得尽量靠右,才能「顺带」引爆后面所有左端点不超过它的区间;遇到左端点超出当前箭射程的气球,再补一支新箭(同样放在它的右端点)。与「线段覆盖」互为对偶:一个用最少的点扎穿所有区间,一个用最多的互不重叠区间。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<pair<long long, long long>> seg(n); // {左端点, 右端点}
for (auto &[l, r] : seg) cin >> l >> r;
// 按右端点从小到大:箭放在当前未被引爆区间的右端点,
// 能把它和后面所有左端点 <= 该点的区间一起引爆
sort(seg.begin(), seg.end(), [](auto &a, auto &b) {
return a.second < b.second;
});
int cnt = 0;
long long cur = LLONG_MIN; // 上一支箭的位置
for (auto &[l, r] : seg) {
if (l > cur) { // 这支箭够不着它,需要一支新箭
cnt++;
cur = r;
}
}
cout << cnt << endl;
return 0;
}
|
移走比赛
移走比赛
题目描述
有 \(n\) 圆比赛,第 \(i\) 场占用时间区间 \([s_i, e_i)\)。两场比赛重叠(一场未结束时另一场已开始)就不能都保留。求最少移走多少场比赛,使剩下的比赛互不重叠。
输入格式
第一行一个整数 \(n\);接下来 \(n\) 行每行两个整数 \(s_i, e_i\)。
输出格式
一个整数,为最少移走的场数。
样例输入
样例输出
(移走 [2,4]:[1,3] 与 [3,5] 端点相接不算重叠,[6,8] 互不影响。)
数据范围
- \(1 \le n \le 10^5\)
- \(0 \le s_i < e_i \le 10^9\)
⬇ 点击下载数据包
移走比赛 AC 代码
解题思路
「线段覆盖」的镜像:那边求最多保留、这边求最少移走,而「最少移走 $= n - $ 最多保留」——同一个按结束时间排序的活动选择贪心,换个问法而已。判定保留时用 \(s_i \ge \text{上一场结束时间}\)(端点相接不算重叠)。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<pair<long long, long long>> seg(n); // {结束时间, 开始时间}
for (auto &[e, s] : seg) cin >> s >> e;
// 按结束时间排序,能留下就留下(活动选择),最少移走 = n - 最多留下
sort(seg.begin(), seg.end());
long long lastEnd = LLONG_MIN;
int kept = 0;
for (auto &[e, s] : seg) {
if (s >= lastEnd) { // 与已选区间不重叠(端点相接允许)
kept++;
lastEnd = e;
}
}
cout << n - kept << endl;
return 0;
}
|
修理牛棚
修理牛棚
题目描述
一排编号 \(1 \sim s\) 的牛棚,其中有 \(c\) 头牛(给出每头牛所在牛棚编号)。要用木板把所有有牛的牛棚都挡住(一块木板可以连续盖若干牛棚),最多只能买 \(m\) 块木板。求木板总长度的最小值。
输入格式
第一行三个整数 \(m, s, c\);接下来 \(c\) 行每行一个整数,为牛所在牛棚的编号。
输出格式
一个整数,为木板最小总长度。
样例输入
| 4 50 18
3
4
6
8
14
15
16
17
21
25
26
27
30
31
40
41
42
43
|
样例输出
数据范围
- \(1 \le m \le 50\),\(1 \le c \le s \le 200\)
修理牛棚 AC 代码
解题思路
逆向贪心:先用一整块木板盖住所有牛(长度 = 最右牛棚 − 最左牛棚 + 1),再把牛之间的空隙从大到小断开 \(m-1\) 个——每断开一个空隙省下一段木板,省得多的先断。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1209 修理牛棚:先用一整块盖住所有牛,再把最大的 m-1 个空隙断开
int main() {
int m, s, c;
cin >> m >> s >> c;
vector<int> a(c);
for (auto& x : a) cin >> x;
sort(a.begin(), a.end());
int total = a[c - 1] - a[0] + 1;
vector<int> gap;
for (int i = 1; i < c; i++) gap.push_back(a[i] - a[i - 1] - 1);
sort(gap.rbegin(), gap.rend());
for (int i = 0; i < m - 1 && i < (int)gap.size(); i++) total -= gap[i];
cout << total << endl;
return 0;
}
|
防晒
防晒
题目描述
\(C\) 头奶牛晒日光浴,第 \(i\) 头需要阳光强度在 \([minSPF_i, maxSPF_i]\) 之间。有 \(L\) 种防晒霜,第 \(i\) 种能把强度稳定为 \(SPF_i\),共有 \(cover_i\) 瓶。每头奶牛必须涂一瓶,求最多能满足多少头奶牛。
输入格式
第一行两个整数 \(C, L\);接下来 \(C\) 行每行两个整数 \(minSPF_i, maxSPF_i\);再接下来 \(L\) 行每行两个整数 \(SPF_i, cover_i\)。
输出格式
一个整数,为最多能满足的奶牛数。
样例输入
样例输出
数据范围
- \(1 \le C, L \le 2500\),\(1 \le minSPF_i \le maxSPF_i \le 1000\),\(1 \le SPF_i \le 1000\),\(1 \le cover_i \le 2500\)
防晒 AC 代码
解题思路
奶牛按 \(minSPF\) 升序处理,每头牛用「能用的瓶中 SPF 最小」的那瓶(交换论证:大 SPF 瓶留给下限更高的牛才不浪费)——排序键 + 匹配的复合贪心。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P2887 防晒:奶牛按 minSPF 升序,每头用「能用瓶中 SPF 最小」的那瓶
int main() {
int c, l;
cin >> c >> l;
vector<pair<int, int>> cow(c); // (minSPF, maxSPF)
for (auto& p : cow) cin >> p.first >> p.second;
sort(cow.begin(), cow.end());
vector<pair<int, int>> lot(l); // (SPF, 瓶数)
for (auto& p : lot) cin >> p.first >> p.second;
int ans = 0;
for (auto& p : cow) {
int best = -1; // 保住大 SPF 瓶给下限更高的牛
for (int i = 0; i < l; i++) {
if (lot[i].second > 0 && lot[i].first >= p.first && lot[i].first <= p.second)
if (best < 0 || lot[i].first < lot[best].first) best = i;
}
if (best >= 0) { ans++; lot[best].second--; }
}
cout << ans << endl;
return 0;
}
|
最少会议室
最少会议室
题目描述
有 \(n\) 圆会议,第 \(i\) 场在时刻 \(s_i\) 开始、\(e_i\) 结束(一个会议室同一时刻只能进行一场;一场在 \(t\) 结束、另一场在 \(t\) 开始可以共用会议室)。求最少需要多少个会议室。
输入格式
第一行一个整数 \(n\);接下来 \(n\) 行每行两个整数 \(s_i, e_i\)。
输出格式
一个整数,为最少会议室数。
样例输入
样例输出
([1,4] 与 [2,5] 重叠,需要两间;[7,9] 开始时前两场都已结束,可复用房间。)
数据范围
- \(1 \le n \le 10^5\)
- \(0 \le s_i < e_i \le 10^9\)
⬇ 点击下载数据包
最少会议室 AC 代码
解题思路
答案 = 同时在进行的会议数的峰值。把所有开始时刻、结束时刻各自排序,双指针扫描:开始时刻更早就新增一间,否则先释放一间,过程中记录峰值。另一个等价理解是事件扫:开始记 \(+1\)、结束记 \(-1\),前缀和的最大值就是答案。与「移走比赛」对照着体会:重叠结构一样,一个删活动、一个加房间。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<long long> st(n), en(n);
for (int i = 0; i < n; i++) cin >> st[i] >> en[i];
sort(st.begin(), st.end());
sort(en.begin(), en.end());
// 开始时刻升序、结束时刻升序各排一遍,双指针数「同时在开」的峰值
int rooms = 0, best = 0, i = 0, j = 0;
while (i < n) {
if (st[i] < en[j]) { rooms++; best = max(best, rooms); i++; }
else { rooms--; j++; } // 有的会议结束了,腾出一个房间
}
cout << best << endl;
return 0;
}
|
扫描 · 配对 · 综合实战
均分纸牌
均分纸牌
题目描述
\(N\) 堆纸牌,第 \(i\) 堆有 \(A_i\) 张,总数是 \(N\) 的倍数。只能在相邻堆之间移动纸牌,求最少移动多少次使每堆张数相同。
输入格式
第一行一个整数 \(N\);第二行 \(N\) 个整数 \(A_i\)。
输出格式
一个整数,为最少移动次数。
样例输入
样例输出
数据范围
- \(1 \le N \le 100\),\(1 \le A_i \le 10000\)
均分纸牌 AC 代码
解题思路
扫描贪心:从左往右扫,当前堆不等于平均值就必须移一次(多了往右送、缺了从右拿,都可以在一次移动里完成),把盈亏累加给下一堆。相邻移动的限制决定了「能一次结算就一次结算」最优。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1031 均分纸牌:从左往右扫,当前堆不平就必须移一次,多退少补给下一堆
int main() {
int n;
cin >> n;
vector<long long> a(n);
long long sum = 0;
for (auto& x : a) { cin >> x; sum += x; }
long long avg = sum / n, cnt = 0;
for (int i = 0; i < n; i++) {
if (a[i] != avg) cnt++;
a[i + 1] += a[i] - avg;
}
cout << cnt << endl;
return 0;
}
|
最大子段和
最大子段和
题目描述
给定长度为 \(n\) 的整数序列(可能有负数),选出非空连续的一段,使其和最大。求这个最大和。
输入格式
第一行一个整数 \(n\);第二行 \(n\) 个整数 \(a_i\)。
输出格式
一个整数,为最大子段和。
样例输入
样例输出
(和最大的段是 4 −1 2 1,总和为 6。)
数据范围
- \(1 \le n \le 10^5\)
- \(|a_i| \le 10^4\)
⬇ 点击下载数据包
最大子段和 AC 代码
解题思路
贪心视角一遍扫:维护「以当前位置结尾的最大子段和」cur——累加上 \(a_i\) 后若 cur < 0,这段负前缀接到任何后面子段上都只会拖累,果断清零重来;过程中记录全局最大 best。注意 best 初值要设成极小(或第一个元素),全负序列时答案是最小的那个负数。这题在 L28 会再见到 DP 视角,两种做法殊途同归。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
long long cur = 0, best = LLONG_MIN;
for (int i = 1; i <= n; i++) {
long long x;
cin >> x;
cur += x;
best = max(best, cur);
if (cur < 0) cur = 0; // 负前缀只会拖累后面的子段,果断舍弃
}
cout << best << endl;
return 0;
}
|
跳跃游戏
跳跃游戏
题目描述
队列游戏:\(n\) 个格子排成一行(从 1 编号),你在格子 1,站在格子 \(i\) 上最多向右跳 \(a_i\) 格(可以跳更少)。判断能否恰好到达格子 \(n\),能输出 YES,否则输出 NO。
输入格式
第一行一个整数 \(n\);第二行 \(n\) 个整数 \(a_i\)。
输出格式
YES 或 NO。
样例输入
样例输出
数据范围
- \(1 \le n \le 10^5\)
- \(0 \le a_i \le 10^5\)
⬇ 点击下载数据包
跳跃游戏 AC 代码
解题思路
从左到右维护最远可达位置 far:扫到格子 \(i\) 时若 \(i > far\),说明 \(i\) 根本到不了,后面全部到不了,直接判 NO;否则用 \(i + a_i\) 更新 far,一旦 far >= n 就是 YES。每个格子只看一眼,\(O(n)\)——「不可达即整体不可达」的单调性是正确性的来源。
AC代码
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
int far = 1; // 目前能到达的最远位置
bool ok = false;
for (int i = 1; i <= n; i++) {
if (i > far) break; // 这个位置根本到不了
far = max(far, i + a[i]);
if (far >= n) { ok = true; break; }
}
cout << (ok ? "YES" : "NO") << endl;
return 0;
}
|
独木桥
独木桥
题目描述
长度为 \(L\) 的独木桥上坐标 \(1 \sim L\) 处有 \(N\) 个士兵,速度均为 1。士兵初始朝向未知,两个士兵相遇时会各自转身(转身不耗时)。求所有士兵全部离开独木桥(到达 0 或 \(L+1\))的最短可能时间和最长可能时间。
输入格式
第一行一个整数 \(L\);第二行一个整数 \(N\);第三行 \(N\) 个整数,为士兵初始坐标。
输出格式
一行两个整数,分别是最短时间与最长时间,用空格分隔。
样例输入
样例输出
数据范围
- \(1 \le L \le 5000\),\(0 \le N \le 5000\),初始坐标互不相同且 \(N \le L\)
独木桥 AC 代码
解题思路
思维转化:两个士兵相遇掉头,等价于互相穿过(继续走原来的方向)——队伍里「谁是谁」根本不重要,只看每个位置有没有人出发。于是最短时间 = 每人走较近一端的最大值,最长时间 = 每人走较远一端的最大值。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1007 独木桥:相遇掉头等价于互相穿过——每人只需选一个方向走到底
int main() {
int L, n;
cin >> L >> n;
int mn = 0, mx = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
mn = max(mn, min(x, L + 1 - x)); // 最短:每人走向较近一端
mx = max(mx, max(x, L + 1 - x)); // 最长:每人走向较远一端
}
cout << mn << " " << mx << endl;
return 0;
}
|
公路
公路
题目描述
公路上有 \(n\) 个站点,站点 \(i\) 与 \(i+1\) 相距 \(v_i\) 公里;站点 \(i\) 的油价为 \(a_i\) 元/升(每个站点只卖整数升)。每升油可行驶 \(d\) 公里。小苞从站点 1 空箱出发开到站点 \(n\),油箱足够大。求最少加油花费。
输入格式
第一行两个整数 \(n, d\);第二行 \(n-1\) 个整数 \(v_i\);第三行 \(n\) 个整数 \(a_i\)。
输出格式
一个整数,为最小花费。
样例输入
| 5 4
10 10 10 10
9 8 9 6 5
|
样例输出
(在站点 1 买 3 升、站点 2 买 5 升、站点 4 买 2 升。)
数据范围
- \(1 \le n \le 10^5\),\(1 \le d, v_i, a_i \le 10^5\)
公路 AC 代码
解题思路
贪心策略:变量 curMin 记录到当前站点为止的最低油价;每一段路都在「历史最低价」把油补足到能走完这一段(整数升向上取整,油可以带余)——贵价站永远不买油,一定不劣。一遍扫描 \(O(n)\),只用变量与取整。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P9749 公路(主解·课内做法):维护至今最低油价,每段路在最低价站补足整升
int main() {
int n;
long long d;
cin >> n >> d;
vector<long long> v(n), a(n);
for (int i = 1; i < n; i++) cin >> v[i];
for (int i = 0; i < n; i++) cin >> a[i];
long long cost = 0, remain = 0; // remain:现有油还能走的公里数
long long curMin = 4e18;
for (int i = 1; i < n; i++) {
curMin = min(curMin, a[i - 1]); // 站点 1..i 中的最低油价
long long buy = max(0LL, (v[i] - remain + d - 1) / d); // 补足本段,向上取整
cost += buy * curMin;
remain += buy * d - v[i];
}
cout << cost << endl;
return 0;
}
|
公路 补充解法:单调栈跳到下一个更便宜站点(可过 100% 数据)
解题思路
进阶写法:用单调栈从右往左预处理 nxt[i] = i 之后第一个油价更低的站点,买油时直接跳到那里按前缀和一次性买够。总花费与主解完全相同,但要用到 J-L25 之后的「单调栈」技巧(S-L03),作拓展视野。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P9749 公路(补充解法·单调栈):直接跳到下一个更便宜的站点一次性买够
int main() {
int n;
long long d;
cin >> n >> d;
vector<long long> v(n), a(n), pre(n, 0);
for (int i = 1; i < n; i++) { cin >> v[i]; pre[i] = pre[i - 1] + v[i]; }
for (int i = 0; i < n; i++) cin >> a[i];
vector<int> nxt(n, n - 1); // nxt[i]:i 之后第一个油价更低的站点
{
vector<int> st;
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && a[st.back()] >= a[i]) st.pop_back();
nxt[i] = st.empty() ? n - 1 : st.back();
st.push_back(i);
}
}
long long cost = 0, remain = 0;
int i = 0;
while (i < n - 1) {
int j = nxt[i];
long long need = pre[j] - pre[i];
long long buy = max(0LL, (need - remain + d - 1) / d);
cost += buy * a[i];
remain += buy * d - need;
i = j;
}
cout << cost << endl;
return 0;
}
|
田忌赛马
田忌赛马
题目描述
田忌和齐王各有 \(n\) 匹马进行 \(n\) 场比赛,每匹马恰好出场一次,对阵由田忌决定。速度高的马获胜,速度相同为平局(双方都不得分)。田忌每胜一场得 1 分,输了和平局都不得分。已知双方每匹马的速度,求田忌最多能得多少分。
输入格式
第一行一个整数 \(n\);第二行 \(n\) 个整数,为田忌各匹马的速度;第三行 \(n\) 个整数,为齐王各匹马的速度。
输出格式
一个整数,为田忌最多能得的分数。
样例输入
样例输出
(用 2 号马消耗齐王最快的 9,剩下 5 胜 3、8 胜 6,得 2 分。)
数据范围
- \(1 \le n \le 10^5\),速度为不超过 \(10^9\) 的正整数
⬇ 点击下载数据包
田忌赛马 AC 代码
解题思路
排序 + 双指针:双方都按速度升序。我的最慢马若能赢对方最慢马,就稳稳赢下这一场;赢不动就拿它去消耗对方最快的马——「最慢的代价换掉最大的威胁」。与「正版田忌」的区别在平局规则:本题平局不得分也不扣分。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// 田忌赛马:排序 + 双指针。赢不了时用最慢的马消耗对方最快的马
int main() {
int n;
cin >> n;
vector<long long> a(n), b(n);
for (auto& x : a) cin >> x;
for (auto& x : b) cin >> x;
sort(a.begin(), a.end());
sort(b.begin(), b.end());
int win = 0;
int i = 0, j = 0, k = n - 1; // i 我的慢马;j 对方慢马;k 对方最快
while (i < n && j <= k) {
if (a[i] > b[j]) { win++; i++; j++; } // 我最慢的马也吃得动对方最慢的:稳赢一场
else { i++; k--; } // 吃不动:拿它消耗对方最快的马
}
cout << win << endl;
return 0;
}
|
公交换乘
公共交通换乘
题目描述
B 市地铁换乘优惠规则:乘一次地铁获得一张优惠票,有效期为 45 分钟(\(t_{bus} - t_{subway} \le 45\)),有效期内可免费搭乘一次票价不超过地铁票价的公交车;优惠票可累积;乘公交时能用优惠票一定用,且优先消耗获得最早的满足条件的票。给定 \(n\) 条按时间升序的乘车记录(0 为地铁、1 为公交),求总花费。
输入格式
第一行一个整数 \(n\);接下来 \(n\) 行每行三个整数 \(type, price, t\),分别表示交通工具(0 地铁 / 1 公交)、票价与开始时刻(分钟)。
输出格式
一个整数,为总花费。
样例输入
| 6
0 10 3
1 5 46
0 12 50
1 3 96
0 5 110
1 6 135
|
样例输出
数据范围
- \(1 \le n \le 10^5\),\(t_i \le 10^9\),\(1 \le price_i \le 1000\)
- 记录按开始时刻升序给出,同一分钟不会有多条记录
公交换乘 AC 代码
解题思路
队列模拟 × 贪心综合(L17 队列回归):地铁票按获得时间先进先出排队;乘公交时先从队首清掉过期票(超过 45 分钟),再找队里第一张票价足够的票消耗——「最早的先用」由题面钦定,恰好就是队列序。注意 45 分钟窗口内至多 45 张票,扫描开销很小。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P5661 公交换乘:地铁票按时间排队;公交用「最早获得且够价」的票
int main() {
int n;
cin >> n;
vector<pair<int, int>> tk; // 未用优惠票 (票价, 时刻),时间升序
int head = 0;
long long cost = 0;
for (int r = 0; r < n; r++) {
int type, price, t;
cin >> type >> price >> t;
if (type == 0) {
cost += price; // 地铁必须花钱
tk.push_back({price, t});
} else {
while (head < (int)tk.size() && t - tk[head].second > 45) head++; // 队首过期
bool used = false;
for (int i = head; i < (int)tk.size(); i++) {
if (t - tk[i].second > 45) break; // 后面只会更晚
if (tk[i].first >= price) { // 最早且够价
used = true;
tk.erase(tk.begin() + i);
break;
}
}
if (!used) cost += price;
}
}
cout << cost << endl;
return 0;
}
|
合并果子
合并果子
题目描述
果园里有 \(n\) 堆果子,第 \(i\) 堆有 \(a_i\) 个。每次可以把任意两堆合并成一堆,消耗的体力等于两堆数目之和。把所有果子合成一堆,求消耗体力的最小值。
输入格式
第一行一个整数 \(n\);第二行 \(n\) 个整数 \(a_i\)。
输出格式
一个整数,为最小体力耗费。
样例输入
样例输出
数据范围
- \(1 \le n \le 10^4\),\(1 \le a_i \le 2 \times 10^4\)
合并果子 AC 代码
解题思路
每次合并最小的两堆(先合并的被算多次,小的该早合并——哈夫曼思想)。主解用「排序 + 两个队列」:原始堆升序一队、新合成堆一队(天然保持升序),每轮从两队队首取最小两个,完全用 L17 已学的队列实现,不需要优先队列。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1090 合并果子:每次合并最小的两堆。排序 + 两个队列(原始堆 / 新合成堆),免去优先队列
int main() {
int n;
cin >> n;
vector<long long> a(n);
for (auto& x : a) cin >> x;
sort(a.begin(), a.end());
queue<long long> q1, q2; // q1 原始堆升序;q2 新合成堆也升序
for (long long x : a) q1.push(x);
auto takeMin = [&]() {
bool useQ1 = !q1.empty() && (q2.empty() || q1.front() <= q2.front());
long long v;
if (useQ1) { v = q1.front(); q1.pop(); }
else { v = q2.front(); q2.pop(); }
return v;
};
long long cost = 0;
for (int r = 1; r < n; r++) {
long long x = takeMin(), y = takeMin();
cost += x + y;
q2.push(x + y);
}
cout << cost << endl;
return 0;
}
|
合并果子 补充解法:小根堆 priority_queue(可过 100% 数据)
解题思路
标准竞赛写法:把所有堆放进小根堆,每次弹出两个最小的合并,再把和放回堆里,直到只剩一堆。比「排序 + 两队列」更直观,但要用到优先队列(堆,S 侧数据结构内容),作拓展视野。
AC代码
| #include <bits/stdc++.h>
using namespace std;
// P1090 合并果子(补充解法·小根堆):priority_queue 每次弹最小两堆
int main() {
int n;
cin >> n;
priority_queue<long long, vector<long long>, greater<>> pq; // 小根堆
for (int i = 0; i < n; i++) {
long long w;
cin >> w;
pq.push(w);
}
long long cost = 0;
while (pq.size() > 1) {
long long a = pq.top(); pq.pop();
long long b = pq.top(); pq.pop();
cost += a + b; // 每次合并代价 = 两堆之和
pq.push(a + b);
}
cout << cost << "\n";
return 0;
}
|