机场廊桥分配问题是一个典型的资源调度优化场景。想象一下,你负责管理一个新建机场的廊桥资源,需要决定如何将有限的廊桥分配给国内和国际航班,使得尽可能多的飞机能够停靠在廊桥(而不是远机位),从而提升乘客体验。
这个问题的核心在于:给定n个廊桥、m1个国内航班和m2个国际航班的起降时间,如何分配廊桥数量(国内区x个,国际区n-x个)才能让停靠廊桥的飞机总数最多?关键在于理解以下几点:
最直观的想法是枚举所有可能的分配方案(国内0~n个,国际n~0个),对每种方案模拟航班停靠过程,最后取最优解。但这种方法时间复杂度为O(n*(m1+m2)*n),当n和m达到1e5时显然不可行。
突破点在于发现:当把所有廊桥分配给单一区域时,每个廊桥的占用情况是确定的。具体来说:
这样,我们只需要:
计算a和b数组需要高效管理廊桥的占用状态。这里使用两个小根堆:
idle堆:存储当前可用的廊桥编号(总保持最小可用编号在堆顶)busy堆:存储正在使用的廊桥,按飞机离开时间排序(最早离开的在堆顶)算法流程:
cpp复制priority_queue<int, vector<int>, greater<>> idle; // 空闲廊桥(小根堆)
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> busy;
// 正在使用的廊桥,pair<离开时间, 廊桥编号>
vector<int> cnt(N); // 记录每个廊桥服务的飞机数
cpp复制auto Do = [&](vector<pair<int, int>>& ab) {
// 清空并初始化堆
while(idle.size()) idle.pop();
while(busy.size()) busy.pop();
for(int i=0; i<N; i++) idle.push(i);
fill(cnt.begin(), cnt.end(), 0);
// 处理每个航班
for(auto& [a, b] : ab) {
// 释放已离开的廊桥
while(busy.size() && busy.top().first <= a) {
idle.push(busy.top().second);
busy.pop();
}
if(idle.empty()) continue; // 无可用廊桥
// 分配编号最小的可用廊桥
int bridge = idle.top();
cnt[bridge]++;
busy.emplace(b, bridge);
idle.pop();
}
// 计算前缀和
vector<int> preSum(N+1);
for(int i=0; i<N; i++)
preSum[i+1] = preSum[i] + cnt[i];
return preSum;
};
cpp复制// 输入处理
int N, M1, M2;
cin >> N >> M1 >> M2;
vector<pair<int,int>> ab1(M1), ab2(M2);
for(auto& p : ab1) cin >> p.first >> p.second;
for(auto& p : ab2) cin >> p.first >> p.second;
// 按到达时间排序
sort(ab1.begin(), ab1.end());
sort(ab2.begin(), ab2.end());
// 计算前缀和
auto preSuma = Do(ab1); // 国内
auto preSumb = Do(ab2); // 国际
// 找最优解
int ans = 0;
for(int i=0; i<=N; i++)
ans = max(ans, preSuma[i] + preSumb[N-i]);
cout << ans;
总复杂度:O((M1+M2)(log M + log N) + N),满足1e5数据量要求
主要消耗在存储航班数据和堆结构:
建议验证这些情况:
如果机场有多条跑道,允许航班同时到达,问题会变得复杂。需要考虑:
如果廊桥可以在国内和国际间动态调配(需要转换时间),可以设计更灵活的算法,如:
不仅考虑最大化廊桥使用,还可以考虑:
在实际机场调度中,这类问题通常会使用更复杂的离散事件仿真模型,结合实时数据动态调整。但本题的解法已经抓住了最核心的贪心思想,是理解资源调度问题的良好起点。
这个问题的解法展示了如何通过巧妙的预处理(计算单区域前缀和)将O(n^2)问题优化为O(n log n),这种思路在许多资源分配问题中都适用。堆结构的高效管理则是实现这一优化的关键数据结构。