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;
}
posted @ 2025-09-12 20:57  qwqSW  阅读(54)  评论(0)    收藏  举报