[JSOI2007] 建筑抢修
按截止时间扫描,最大堆维护已选工期;超时则用更短任务替换最长任务。
OJ: luogu
题目 ID: P4053
难度:普及+/提高
标签:贪心最大堆调度python
日期: 2026-07-16 21:00
形式化题目
给定
思路
先看一个可以直接验证想法的朴素解:
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 22;
struct Building {
long long need_time;
long long deadline;
};
int n;
Building buildings[MAXN];
bool cmp_building(const Building &a, const Building &b) {
if (a.deadline != b.deadline) {
return a.deadline < b.deadline;
}
return a.need_time < b.need_time;
}
bool check_subset(int mask) {
vector<Building> chosen;
for (int i = 0; i < n; i++) {
if (mask & (1 << i)) {
chosen.push_back(buildings[i]);
}
}
sort(chosen.begin(), chosen.end(), cmp_building);
long long cur = 0;
for (int i = 0; i < (int)chosen.size(); i++) {
cur += chosen[i].need_time;
if (cur > chosen[i].deadline) {
return false;
}
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 0; i < n; i++) {
cin >> buildings[i].need_time >> buildings[i].deadline;
}
int ans = 0;
for (int mask = 0; mask < (1 << n); mask++) {
if (check_subset(mask)) {
ans = max(ans, __builtin_popcount((unsigned)mask));
}
}
cout << ans << '\n';
return 0;
}暴力枚举所有子集,对每个子集按截止时间排序后检查可行性。瓶颈是
为什么按截止时间排序是最优顺序?
假设选定了一组任务要完成,怎样安排顺序最不容易超时?直觉上截止时间早的先做。严格证明用交换论证:若存在任务 A、B 满足
这个观察的意义:一旦确定了顺序,问题就变成了"从左到右,选不选当前任务"的决策问题。
装不下时,删掉耗时最长的那个——为什么对?
处理到第
严格证明用归纳不变量:
处理完前
个任务(按截止时间排序)后,算法维护的集合 是 的所有可行子集中数量最大的,且在数量最大的所有可行子集中总耗时最小的。
情况 A:
情况 B:
为什么超时后只需删一次,不用循环删到不超时?
因为一次删除必然足够,第二次永远不会发生:
- 处理第
个任务前,已选集合是可行的,即总耗时 (或之前的某个截止时间)。 - 按截止时间升序处理,所以
,于是 。 - 加入
后可能超时,但弹出的 是堆里最大的工期,而堆里包含 ,所以 。 - 删掉后总耗时变为
,一次删除就回到不超时。
C++ 版使用 STL priority_queue(默认大根堆)维护已选任务中最长的工期,堆顶就是需要替换的目标。
如果想对齐 rbook 的 heap 模板,也可以手写 up/down 完成同样的操作,见 main2.cpp。
Python 知识
sorted(..., key=lambda item: item[1])明确按截止时间排序。- 负工期最大堆让
-heap[0]是已选最长任务。 heapreplace一次替换堆顶,并返回被替换负值用于修正总时间。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 150005;
struct Building {
long long need_time;
long long deadline;
};
int n;
Building buildings[MAXN];
bool cmp_building(const Building &a, const Building &b) {
if (a.deadline != b.deadline) {
return a.deadline < b.deadline;
}
return a.need_time < b.need_time;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> buildings[i].need_time >> buildings[i].deadline;
}
sort(buildings + 1, buildings + n + 1, cmp_building);
// 大根堆:priority_queue 默认堆顶最大,堆顶就是已选任务中最长的工期。
priority_queue<long long> selected;
long long total_time = 0; // 已选任务的总工期
for (int i = 1; i <= n; i++) {
total_time += buildings[i].need_time;
selected.push(buildings[i].need_time);
// 当前任务超时:扔掉已选任务中工期最长的,给后续留余量。
if (total_time > buildings[i].deadline) {
total_time -= selected.top();
selected.pop();
}
}
cout << selected.size() << '\n';
return 0;
}/**
* 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-08-10 17:19
* update_at: 2026-08-10 17:19
*/
// main2.cpp:手写堆版本,风格对齐 rbook 的 heap 模板(h[0] 不使用,up/down 维护堆序)。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 150005;
struct Building {
long long need_time;
long long deadline;
};
int n;
Building buildings[MAXN];
// 最大堆:手写实现,风格对齐 rbook 的 heap 模板(h[0] 不使用)。
template <typename T>
struct MaxHeap {
vector<T> h;
MaxHeap() {
h.push_back(T());
}
int size() const {
return (int)h.size() - 1;
}
bool empty() const {
return size() == 0;
}
T top() const {
return h[1];
}
void up(int u) {
while (u > 1 && h[u] > h[u / 2]) {
swap(h[u], h[u / 2]);
u /= 2;
}
}
void down(int u) {
while (true) {
int best = u;
int left = u * 2;
int right = u * 2 + 1;
if (left <= size() && h[left] > h[best]) best = left;
if (right <= size() && h[right] > h[best]) best = right;
if (best == u) break;
swap(h[u], h[best]);
u = best;
}
}
void push(const T &x) {
h.push_back(x);
up(size());
}
void pop() {
if (empty()) return;
h[1] = h.back();
h.pop_back();
if (!empty()) down(1);
}
};
bool cmp_building(const Building &a, const Building &b) {
if (a.deadline != b.deadline) {
return a.deadline < b.deadline;
}
return a.need_time < b.need_time;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> buildings[i].need_time >> buildings[i].deadline;
}
sort(buildings + 1, buildings + n + 1, cmp_building);
MaxHeap<long long> selected; // 已选任务的工期,堆顶是最大的那个
long long total_time = 0; // 已选任务的总工期
for (int i = 1; i <= n; i++) {
total_time += buildings[i].need_time;
selected.push(buildings[i].need_time);
// 当前任务超时:扔掉已选任务中工期最长的,给后续留余量。
if (total_time > buildings[i].deadline) {
total_time -= selected.top();
selected.pop();
}
}
cout << selected.size() << '\n';
return 0;
}import heapq
import sys
data = iter(map(int, sys.stdin.buffer.read().split()))
buildings = sorted(((next(data), next(data)) for _ in range(next(data))),
key=lambda item: item[1])
chosen = []
elapsed = 0
for duration, deadline in buildings:
if elapsed + duration <= deadline:
elapsed += duration
heapq.heappush(chosen, -duration)
elif chosen and -chosen[0] > duration:
elapsed += duration + heapq.heapreplace(chosen, -duration)
print(len(chosen))复杂度
排序和堆操作总计
总结
以截止时间推进时,固定已选数量下总工期越小越优,因此超时应淘汰最长任务。