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;
}
浙公网安备 33010602011771号