把农民按单价升序排序后线性扫描,能整批拿就整批拿,最后一位只取凑满需求的部分,得到最小花费。
OJ: roj
题目 ID: 2009
难度:入门
标签:贪心排序python
创建: 2026-10-08 07:03
更新: 2026-10-08 07:03
形式化题目
有
设从农民
数据范围:
正解
思路
朴素想法。 直接模拟采购:每一步在尚有存货的农民里挑单价最低的那一位,能买多少买多少;
买光了他的库存就回去重新找下一个最低价,直到凑满
瓶颈在哪? 单价
贪心为什么正确? 交换论证:设最优解中农民
- 只要农民
还有货没买,就绝不去买任何单价比他贵的牛奶; - 因此所有农民被"用尽"的先后顺序,就是单价从小到大的顺序。
这两条合起来就是一句:按单价升序依次把每个农民的货拿光,直到需求被凑满。
算法步骤。
- 把
个农民的 二元组按 升序排序, 相同则顺序任意(牛奶同质)。 - 记
remain = N,cost = 0,从头扫描排好序的序列:- 若
remain:整批买下,cost += A_i * P_i,remain -= A_i; - 若
remain:只买remain个单位,cost += remain * P_i,remain归零后立即停止。
- 若
- 输出
cost。
几个易错点。
- 最后一位必须截断:买的是
min(A_i, remain)个单位,而不是把该农民的全部库存买光。 - 排序要按结构体整体排,不能只把价格数组排序而让供应量失去配对。
- 比较器用严格小于(
P_i < P_j),写成<=会破坏严格弱序,在存在同价农民时行为未定义。 的边界:需求为 0 时循环第一轮就不该买入,直接输出 0;把循环条件写成remain > 0即可自然处理。且 的情况由题面"总供给足够"排除。
以样例为例,输入
依次采购:买入 10 个 @3 花 30,剩 90;买入 20 个 @5 花 100,剩 70;
买入 30 个 @6 花 180,剩 40;轮到单价 8 的农民,只买 40 个凑满,花 320。
总计
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-08 07:03
# update_at: 2026-10-08 07:03
import sys
def greedy_cost(need: int, farmers: list[tuple[int, int]]) -> int:
"""按单价从低到高采购,返回凑满 need 加仑的最小花费。"""
remain = need # 还差多少牛奶没买到
cost = 0 # 累计花费,量级 2e6 * 1000,Python 大整数天然安全
for price, amount in sorted(farmers): # 元组比较,主键就是单价
if remain <= 0:
break # 需求已满足(含 need = 0 的边界)
take = min(amount, remain) # 剩下的第一个人只需买一部分
cost += take * price
remain -= take
return cost
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
need, m = next(data), next(data)
farmers = [(next(data), next(data)) for _ in range(m)] # (价格 P_i, 供应量 A_i)
print(greedy_cost(need, farmers))
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-08 07:03
* update_at: 2026-10-08 07:03
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXM = 5005; // 农民数量上限
struct Farmer {
ll price; // 每加仑牛奶的单价 P_i
ll amount; // 该农民一天能提供的最大牛奶量 A_i
};
Farmer f[MAXM]; // f[1..m] 存每个农民的价格与供应量
// 按单价升序排序:单价相同时谁在前无所谓,牛奶完全同质
bool cmp_price(const Farmer &x, const Farmer &y) {
return x.price < y.price;
}
int main() {
ll need; // 每日需求量 N
ll m; // 农民数量 M
if (scanf("%lld %lld", &need, &m) != 2) {
return 0;
}
for (ll i = 1; i <= m; i++) {
scanf("%lld %lld", &f[i].price, &f[i].amount);
}
sort(f + 1, f + m + 1, cmp_price);
ll remain = need; // 还差多少牛奶没买到
ll cost = 0; // 累计花费,最坏 2e6 * 1000 = 2e9,必须用 64 位
for (ll i = 1; i <= m && remain > 0; i++) {
if (f[i].amount <= remain) {
// 该农民的牛奶全部买下还不够(或刚好够),整批拿走
cost += f[i].amount * f[i].price;
remain -= f[i].amount;
} else {
// 只买凑满需求的那一部分,买完就结束
cost += remain * f[i].price;
remain = 0;
}
}
printf("%lld\n", cost);
return 0;
}复杂度
- 时间:排序
,扫描 (每位农民 次算术),总计 。 时排序比较约 次,远小于 1000 ms 的限制。 - 空间:只需存
个 二元组, ,约几十 KB,远低于 128 MB 限制。 - 与 C++ 解法的关系:
main.py与main.cpp是同一算法的两个语言版本—— 两者都是"排序 + 线性扫描",复杂度同为。 Python 侧用 sorted()排元组(元组比较默认以价格为主键)以及min(amount, remain)表达截断,写法更短但语义完全一致;本题数据量下 Python 也没有 TLE 风险。
总结
- 静态的次序不要动态求:当决策依据(这里是单价)不随时间改变时,先排序再线性扫描,
就能把朴素做法的
降到 。 - 贪心的正确性靠交换论证:把"用更贵的牛奶"换成"用更便宜的牛奶"总能降低成本, 因此最优解必然优先用尽最便宜的份额。牛奶连续可拆、单价恒定,是这一步成立的前提。
- 最后一位要截断:只有能整批拿下的农民才买光,第一个超过剩余需求的农民只取所需部分。
- 答案 64 位:
已经逼近 int上界, C++ 侧必须用long long累计花费。