P7913 [CSP-S 2021] 廊桥分配
upd on 10.23:感觉之前的代码比较冗长,第二次写这个题后又写了一版可读性更强的代码,讲解部分也会有适当调整。
思路
首先我们是可以把两个区拆开考虑的,以下以国内区为例:
我们先不考虑廊桥个数的限制。由于飞机是遵循先来先到的原则,所以我们不需要帮忙排飞机了,直接让飞机停在当前编号最小的空闲廊桥。
这样当每一班飞机到机场时,我们可以模拟出来这架飞机会停在哪个廊桥。
好,现在我们加上廊桥个数的限制。我们在考虑国内区的时候枚举当前分到的廊桥个数,如果分到了 \(i\) 个廊桥的话,那么 \(i+1\) 及以后的廊桥都可以被视作远机位了。
道理也很简单:如果这架飞机停在了 \(i+1\) 及往后的廊桥(假设为 \(x\) ),那么根据廊桥先来先得的原则,这架飞机能停的编号最小的廊桥也就是 \(x\) 了。
也就是说,前面 \(x-1\) 个廊桥都停满飞机了,还来的都比这架飞机早。那么我们就选了 \(i\) 个廊桥,如果按照先来先到的分法,这架飞机肯定吃亏。
所以,当只有 \(i\) 个廊桥时,它是无论如何都停不进去的。
这样一来,如果我们维护这个区里每个廊桥停的飞机数量(用 \(cnt_j\) 表示编号为 \(j\) 的廊桥能停多少个飞机),那么分到 \(i\) 个廊桥时,这个区就能停 \(\sum\limits_{j=1}^{i} {cnt_j}\) 个飞机。
很显然上面的累加和可以用前缀和数组优化。至此我们的思路就有了。
实现
将两个区拆开考虑,对于每个区,使用两个优先队列模拟飞机降落的过程。
具体来说,首先根据飞机到达时间升序排序,然后枚举 \(1\) ~ \(m1\)(国际区是 \(1\) ~ \(m2\) )内所有飞机,相当于是模拟每班飞机到达时停机的过程。
这里我定义了两个优先队列,一个存储的是当前空闲廊桥编号(代码中为 \(lq\) ),按编号由小到大升序排序。
另一个存储有飞机的廊桥(代码中为 \(q\) ),共有两个信息,一个是当前停靠飞机的离开时间,一个是当前廊桥的编号。
当第 \(j\) 架飞机到达时,首先比较 \(q\) 队首飞机的离开时间是否小于当前飞机的到达时间,如果是则说明该飞机已离开,将 \(q\) 队首的廊桥编号扔到 \(lq\) 里,并将 \(q\) 队首元素出队列。
这样操作下来,已经离开的飞机都处理完了,接下来第 \(j\) 架飞机要进入的廊桥就是 \(lq\) 的队首元素(假设为 \(x\) )。
我们只要让 \(cnt_x +1\) ,并让 \(lq\) 将 \(x\) 出队, \(q\) 队列把 \(x\) 及第 \(j\) 架飞机的离开时间入队即可。
将两个区停靠情况模拟完成后,我们进行前缀和处理后(这里我国内区的前缀和数组为 \(sum1\) ,国际区为 \(sum2\) ),答案就是 \(\max\limits_{i=0}^{n} sum1_i+sum2_{n-i}\)。
注意每次操作优先队列是要判断队列是否为空。
AC code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<48){
if(c=='-') f=-1;
c=getchar();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
const int N=1e5+5;
int n,m[3],sum[3][N],cnt[3][N];
priority_queue<int,vector<int>,greater<int> > lq;
//lq:存储当前空闲廊桥的编号(按编号从小到大排序)
struct sw{
int l,r,id;
bool operator <(const sw SW)const{
return r>SW.r;
}
}a[3][N];
priority_queue<sw> q;
//q:存储当前有飞机的廊桥的编号及飞机的离开时间(按飞机离开时间从早到晚排序)
inline bool cmp(sw x,sw y){
return x.l<y.l;
}
inline void INIT(){//初始化
while(!q.empty()) q.pop();
while(!lq.empty()) lq.pop();
for(int i=1;i<=n;i++){
lq.push(i);
}
}
inline void solve(int id){
//算国际机场时可能有国内机场的残留,要处理掉
INIT();
sort(a[id]+1,a[id]+m[id]+1,cmp);
for(int i=1;i<=m[id];i++){
//tips:一定要先判断两个优先队列是否为空,否则会有很多奇奇怪怪的错误
//idlq:编号最小的空闲廊桥编号
while(!q.empty()&&q.top().r<a[id][i].l){
//i飞机到达前就离开机场的飞机要出队列
int idlq=q.top().id;
q.pop();
lq.push(idlq);
}
if(lq.empty()){
continue;
}
int idlq=lq.top();lq.pop();
q.push((sw){a[id][i].l,a[id][i].r,idlq});
cnt[id][idlq]++;
}
for(int i=1;i<=n;i++){
sum[id][i]=sum[id][i-1]+cnt[id][i];
}
}
signed main(){
n=read(),m[1]=read(),m[2]=read();
for(int i=1;i<=m[1];i++){
a[1][i].l=read(),a[1][i].r=read();
}
for(int i=1;i<=m[2];i++){
a[2][i].l=read(),a[2][i].r=read();
}
//国内机场
solve(1);
//国际机场
solve(2);
int ans=0;
for(int i=0;i<=n;i++){//枚举分给国际机场多少个
ans=max(ans,sum[1][i]+sum[2][n-i]);
}
printf("%lld",ans);
return 0;
}

浙公网安备 33010602011771号