先分别模拟国内和国际航班在最小可用廊桥编号策略下的收益前缀和,再枚举两区廊桥数量分配取最大值。
OJ: luogu
题目 ID: P7913
难度:普及+/提高-
标签:模拟贪心优先队列
创建: 2026-07-06 08:46
更新: 2026-10-07 10:45
形式化题目
机场有
解法路线
题面给出了三个真实的分数档位:
| 档位 | 真实约束 | 做法 | 瓶颈 |
|---|---|---|---|
| 枚举分配再逐区模拟 | |||
| 收益前缀和 + 枚举分配 | 无 | ||
| 同上 | 无 |
暴力解法:
适用范围
思路
枚举国内区分到
代码
/**
* 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-01 22:39
* update_at: 2026-10-01 22:39
*/
// subtask_20.cpp:20% 数据 n ≤ 100, m1+m2 ≤ 100,枚举国内分几个廊桥再模拟。
// 时间 O(n · m log m),空间 O(m)。覆盖 20% 档。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXM = 105;
// 航班:抵达时刻和离开时刻。
struct Flight {
int arrive;
int leave;
};
ll n, m1, m2;
Flight domestic[MAXM], international_flight[MAXM];
bool cmp_flight(const Flight &a, const Flight &b) {
return a.arrive < b.arrive;
}
// 模拟一个区域分配 bridge_count 个廊桥,返回能接的航班数。
int simulate(Flight flights[], ll m, ll bridge_count) {
if (bridge_count == 0) {
return 0;
}
sort(flights + 1, flights + m + 1, cmp_flight);
priority_queue<int, vector<int>, greater<int> > free_bridge;
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > busy;
for (ll i = 1; i <= bridge_count; i++) {
free_bridge.push((int)i);
}
int answer = 0;
for (ll i = 1; i <= m; i++) {
while (!busy.empty() && busy.top().first < flights[i].arrive) {
free_bridge.push(busy.top().second);
busy.pop();
}
if (!free_bridge.empty()) {
int id = free_bridge.top();
free_bridge.pop();
answer++;
busy.push(make_pair(flights[i].leave, id));
}
}
return answer;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m1 >> m2;
for (ll i = 1; i <= m1; i++) {
cin >> domestic[i].arrive >> domestic[i].leave;
}
for (ll i = 1; i <= m2; i++) {
cin >> international_flight[i].arrive >> international_flight[i].leave;
}
// 先排序一次,后续 simulate 不会改变顺序。
sort(domestic + 1, domestic + m1 + 1, cmp_flight);
sort(international_flight + 1, international_flight + m2 + 1, cmp_flight);
// 枚举国内分几个廊桥,取两种分配的最大值。
int answer = 0;
for (ll domestic_bridge = 0; domestic_bridge <= n; domestic_bridge++) {
int now = simulate(domestic, m1, domestic_bridge)
+ simulate(international_flight, m2, n - domestic_bridge);
answer = max(answer, now);
}
cout << answer << '\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-10-01 23:21
* update_at: 2026-10-01 23:21
*/
// subtask_40.cpp:40% 数据 n ≤ 5000, m1+m2 ≤ 5000,用收益前缀和 + 枚举分配。
// 核心思路:对每个区域一次模拟出"分配 i 个廊桥能接多少航班"的前缀和,再枚举分配取最大值。
// 相比 20% 暴力,不再对每个分配重新模拟,而是复用前缀和,时间降到 O(m log m + n)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 5005;
// 航班:抵达时刻和离开时刻(均 ≤ 10^8,int 足够)。
struct Flight {
int arrive;
int leave;
};
ll n, m1, m2;
Flight domestic[MAXN], international_flight[MAXN];
int cnt_domestic[MAXN], cnt_international[MAXN]; // 分配 i 个廊桥时新增接的航班数
int sum_domestic[MAXN], sum_international[MAXN]; // 前缀和:分配 ≤ i 个廊桥共接多少航班
bool cmp_flight(const Flight &a, const Flight &b) {
return a.arrive < b.arrive;
}
// 模拟一个区域:分配最多 n 个廊桥,用最小可用编号策略,
// 统计每个廊桥编号各接了多少航班,最后做前缀和。
void calc(Flight flights[], ll m, int result[]) {
sort(flights + 1, flights + m + 1, cmp_flight);
// 最小堆:空闲廊桥编号(优先用编号最小的)。
priority_queue<int, vector<int>, greater<int> > free_bridge;
// 最小堆:(离开时刻, 廊桥编号),按离开时刻排序。
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > busy;
ll limit = min(n, m);
for (ll i = 1; i <= limit; i++) {
free_bridge.push((int)i);
}
for (ll i = 1; i <= m; i++) {
while (!busy.empty() && busy.top().first < flights[i].arrive) {
free_bridge.push(busy.top().second);
busy.pop();
}
if (!free_bridge.empty()) {
int id = free_bridge.top();
free_bridge.pop();
result[id]++;
busy.push(make_pair(flights[i].leave, id));
}
}
// 前缀和:result[i] = 分配 ≤ i 个廊桥共接多少航班。
for (ll i = 1; i <= n; i++) {
result[i] += result[i - 1];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m1 >> m2;
for (ll i = 1; i <= m1; i++) {
cin >> domestic[i].arrive >> domestic[i].leave;
}
for (ll i = 1; i <= m2; i++) {
cin >> international_flight[i].arrive >> international_flight[i].leave;
}
calc(domestic, m1, sum_domestic);
calc(international_flight, m2, sum_international);
// 枚举国内分 i 个廊桥,国际分 n-i 个,取最大值。
int ans = 0;
for (ll i = 0; i <= n; i++) {
ans = max(ans, sum_domestic[i] + sum_international[n - i]);
}
cout << ans << '\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-10-01 22:39
* update_at: 2026-10-01 22:39
*/
// main.cpp:满分做法,用最小可用廊桥编号模拟每个区域,统计分配 i 个廊桥能接多少航班。
// 核心思路:先对每个区域独立算出"分配 i 个廊桥能接的航班数"前缀和,再枚举国内/国际分几个廊桥取最大值。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 100005;
// 航班:抵达时刻和离开时刻(均 ≤ 10^8,int 足够)。
struct Flight {
int arrive;
int leave;
};
ll n, m1, m2; // 廊桥总数、国内航班数、国际航班数
Flight domestic[MAXN], international_flight[MAXN];
int cnt_domestic[MAXN], cnt_international[MAXN]; // 分配 i 个廊桥时新增接的航班数
int sum_domestic[MAXN], sum_international[MAXN]; // 前缀和:分配 ≤ i 个廊桥共接多少航班
// 按抵达时刻排序。
bool cmp_flight(const Flight &a, const Flight &b) {
return a.arrive < b.arrive;
}
// 模拟一个区域:分配最多 n 个廊桥,用最小可用编号策略,
// 统计每个廊桥编号各接了多少航班,最后做前缀和。
void calc(Flight flights[], ll m, int result[]) {
sort(flights + 1, flights + m + 1, cmp_flight);
// 最小堆:空闲廊桥编号(优先用编号最小的)。
priority_queue<int, vector<int>, greater<int> > free_bridge;
// 最小堆:(离开时刻, 廊桥编号),按离开时刻排序。
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > busy;
ll limit = min(n, m);
for (ll i = 1; i <= limit; i++) {
free_bridge.push((int)i);
}
for (ll i = 1; i <= m; i++) {
// 先释放所有已离开的廊桥。
while (!busy.empty() && busy.top().first < flights[i].arrive) {
free_bridge.push(busy.top().second);
busy.pop();
}
if (!free_bridge.empty()) {
int id = free_bridge.top();
free_bridge.pop();
result[id]++;
busy.push(make_pair(flights[i].leave, id));
}
}
// 前缀和:result[i] = 分配 ≤ i 个廊桥共接多少航班。
for (ll i = 1; i <= n; i++) {
result[i] += result[i - 1];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m1 >> m2;
for (ll i = 1; i <= m1; i++) {
cin >> domestic[i].arrive >> domestic[i].leave;
}
for (ll i = 1; i <= m2; i++) {
cin >> international_flight[i].arrive >> international_flight[i].leave;
}
calc(domestic, m1, sum_domestic);
calc(international_flight, m2, sum_international);
// 枚举国内分 i 个廊桥,国际分 n-i 个,取最大值。
int ans = 0;
for (ll i = 0; i <= n; i++) {
ans = max(ans, sum_domestic[i] + sum_international[n - i]);
}
cout << ans << '\n';
return 0;
}复杂度
- 时间复杂度:
。 - 空间复杂度:
。
总结
本题的关键是把"分配廊桥数量"和"区内先到先得模拟"解耦:先分别算出每个区域在不同廊桥数量下的收益前缀和,再枚举合并。区内模拟必须使用最小可用廊桥编号,这样每个编号的使用次数才能做前缀和,表示不同廊桥数量下的答案。
三个分数档位串起来是一条"从重复模拟到前缀和"的路线:暴力枚举分配再逐区模拟 → 一次模拟 + 前缀和复用 → 扩大数组规模。真正需要记住的是"最小可用编号策略使得每个编号的使用次数可做前缀和"这一关键观察。
本文的 main.cpp 已用逐区模拟版 brute.cpp 进行 300 组随机对拍;subtask_40.cpp 也已对拍通过。
