CSP-2021 廊桥分配题解

一道有点意思的模拟题

由于本蒟蒻水平过低 考场上从 \(O(n^3)\) 写到 \(O(n\log{n})\) 用了近两个小时,,,


首先考虑 \(O(n^3)\) 暴力。枚举给国内航班分配 \(i(0≤i≤n)\) 个廊桥,给国际航班分配 \((n-i)\) 个廊桥所能停靠的最多航班数。对于每一种枚举出的分配方案,跑一遍 \(O(n^2)\) 的暴力,即先枚举第 \(j\) 个航班 \((1≤j≤m)\) ,再用 \(k(1≤k≤m)\) 枚举出当前空闲的最小编号廊桥。

预计得分 \(20\)但鉴于€€£的淼数据应该能过更多)。


通过对 \(O(n^3)\) 算法的设计,不难得出结论:由于航班出入遵循先来后到的原则,所以不论分配多少个廊桥,航班的停放顺序都不变(远机位实际上也可以被视作廊桥的一部分,只是从不能枚举到)。

因此只需要在枚举方案前跑一遍 \(O(n^2)\) 枚举(做法如上所述),再通过计算前缀和计算出前 \(i(0≤i≤n)\) 个廊桥能停放多少个国内/外航班,最后再枚举分配方案即可。至此,形成 \(O(n^2)\) 算法。预计得分 \(40\)


若想进一步优化算法,使其时间复杂度能适应本题 \(n,m_1+m_2∈[1,1×10^5]\) 的数据规模,则必须优化预处理的时间复杂度到 \(O(n\log{n})\)

由于每次航班降落都会进入编号最小的空闲廊桥中,所以采用最小堆维护这个最小编号,即若有飞机降落,则将堆顶元素弹出;若有飞机起飞,则将该航班所停靠的廊桥编号再插入堆中。

但还是由于先来后到的原则,所以不能直接通过每个航班停靠时间起止点来进行如上操作,需要将其降落和起飞看作两个事件,将所有飞机起降事件记录并按时间排序后再进行考虑(原理不难理解,当前某个廊桥的状态取决于停靠在它上的飞机起降时间)。

综上所述,将所有国内外航班起降事件记录后排序并用最小堆模拟廊桥分配,最后用 \(O(n)\) 循环枚举最大停靠数即为本题AC思路。


  • 附上AC代码如下
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#define RI register int
using namespace std;
const int maxn=1e5+10;
typedef struct
{
	int l,r;/*飞机停靠时间起止点*/
	int bel;/*飞机停靠的廊桥编号*/
}range;/*每个停靠的航班*/
typedef struct
{
	int ord/*所对应航班编号*/,tim/*发生时间*/;
	bool typ;/*typ=0表示降落 typ=1表示起飞*/
}event;/*飞机起降事件*/
struct h
{
	int ord;
	bool operator <(const h &x)const
	{
		return x.ord<ord;
	}	
};/*因为不会写STL最小堆 所以此处用结构体+重载运算符代替*/
event evt1[maxn<<1],evt2[maxn<<1];/*国内和国外航班起降事件*/
range a1[maxn],a2[maxn];/*国内和国外航班*/
int n,m1,m2,cnt1/*国内航班起降事件总数*/,cnt2/*国外航班起降事件总数*/,ans;
int need1[maxn],need2[maxn]/*need[i]表示需要i个廊桥所能停靠飞机数量*/,sn1[maxn],sn2[maxn]/*前缀和*/;
priority_queue<h> q;/*最小堆存储当前状态下空闲的最小编号廊桥*/
inline bool cmp(event x,event y);/*按时间先后排序*/
inline void init(void);
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n >> m1 >> m2;
	for(RI i=1;i<=m1;i++) 
	{
		/*记录第i个航班停靠时间起止点*/ 
		cin >> a1[i].l >> a1[i].r;
		/*将第i个航班转化为两个事件*/
		evt1[++cnt1].tim=a1[i].l;evt1[cnt1].typ=0;evt1[cnt1].ord=i; 
		evt1[++cnt1].tim=a1[i].r;evt1[cnt1].typ=1;evt1[cnt1].ord=i;
	}
	/*对国外航班做同样处理*/
	for(RI i=1;i<=m2;i++) 
	{
		cin >> a2[i].l >> a2[i].r;
		evt2[++cnt2].tim=a2[i].l;evt2[cnt2].typ=0;evt2[cnt2].ord=i;
		evt2[++cnt2].tim=a2[i].r;evt2[cnt2].typ=1;evt2[cnt2].ord=i;
	}
	init();
	/*枚举给国内航班安排i个廊桥 国际航班安排(n-i)个廊桥所能停靠的最多航班数*/ 
	for(RI i=0;i<=n;i++) ans=max(ans,sn1[i]+sn2[n-i]);
	printf("%d",ans);
	return 0;
}
inline bool cmp(event x,event y)
{
	return x.tim<y.tim;
}
inline void init(void)
{
	/*先将国内外航班起降事件分别按时间顺序排序*/
	sort(evt1+1,evt1+1+cnt1,cmp);sort(evt2,evt2+1+cnt2,cmp);
	/*先对国内航班进行预处理*/
	while(!q.empty()) q.pop();
	for(RI i=1;i<=n;i++) q.push((h){i});
	for(RI i=1;i<=cnt1;i++)
		if(!evt1[i].typ)/*当前事件为有航班降落*/ 
		{
			if(q.empty()) continue;/*所有廊桥都已占满*/
			RI availble=q.top().ord;q.pop();/*取出最小编号空闲廊桥*/
			need1[availble]++;/*增加最小编号空闲廊桥停机数*/ 
			a1[evt1[i].ord].bel=availble;/*记录当前航班所属廊桥*/
		}
		else if(a1[evt1[i].ord].bel)/*当前事件为有航班起飞*/
			q.push((h){a1[evt1[i].ord].bel});/*归还所占据廊桥*/ 
	while(!q.empty()) q.pop();
	for(RI i=1;i<=n;i++) q.push((h){i});		
	for(RI i=1;i<=cnt2;i++)
		if(!evt2[i].typ)
		{
			if(q.empty()) continue;
			RI availble=q.top().ord;q.pop();
			need2[availble]++;
			a2[evt2[i].ord].bel=availble;
		}
		else if(a2[evt2[i].ord].bel)
			q.push((h){a2[evt2[i].ord].bel});
	/*计算前缀和 便于在O(1)时间内取出第给国内/外分配若干个廊桥所能停放的航班数*/		
	for(RI i=1;i<=n;i++) 
	{
		sn1[i]=sn1[i-1]+need1[i];
		sn2[i]=sn2[i-1]+need2[i];
	} 		
	return;		
}
posted @ 2022-04-19 20:12  UOB  阅读(89)  评论(0)    收藏  举报