[CSP-S 2021] 廊桥分配
题目描述
当一架飞机抵达机场时,可以停靠在航站楼旁的廊桥,也可以停靠在位于机场边缘的远机位。乘客一般更期待停靠在廊桥,因为这样省去了坐摆渡车前往航站楼的周折。然而,因为廊桥的数量有限,所以这样的愿望不总是能实现。
机场分为国内区和国际区,国内航班飞机只能停靠在国内区,国际航班飞机只能停靠在国际区。一部分廊桥属于国内区,其余的廊桥属于国际区。
L 市新建了一座机场,一共有 n n n 个廊桥。该机场决定,廊桥的使用遵循“先到先得”的原则,即每架飞机抵达后,如果相应的区(国内/国际)还有空闲的廊桥,就停靠在廊桥,否则停靠在远机位(假设远机位的数量充足)。该机场只有一条跑道,因此不存在两架飞机同时抵达的情况。
现给定未来一段时间飞机的抵达、离开时刻,请你负责将 n n n 个廊桥分配给国内区和国际区,使停靠廊桥的飞机数量最多。
输入格式
输入的第一行,包含三个正整数 n , m 1 , m 2 n, m_1, m_2 n,m1,m2,分别表示廊桥的个数、国内航班飞机的数量、国际航班飞机的数量。
接下来 m 1 m_1 m1 行,是国内航班的信息,第 i i i 行包含两个正整数 a 1 , i , b 1 , i a_{1, i}, b_{1, i} a1,i,b1,i,分别表示一架国内航班飞机的抵达、离开时刻。
接下来 m 2 m_2 m2 行,是国际航班的信息,第 i i i 行包含两个正整数 a 2 , i , b 2 , i a_{2, i}, b_{2, i} a2,i,b2,i,分别表示一架国际航班飞机的抵达、离开时刻。
每行的多个整数由空格分隔。
输出格式
输出一个正整数,表示能够停靠廊桥的飞机数量的最大值。
样例 #1
样例输入 #1
3 5 4
1 5
3 8
6 10
9 14
13 18
2 11
4 15
7 17
12 16
样例输出 #1
7
样例 #2
样例输入 #2
2 4 6
20 30
40 50
21 22
41 42
1 19
2 18
3 4
5 6
7 8
9 10
样例输出 #2
4
样例 #3
样例输入 #3
见附件中的 airport/airport3.in
样例输出 #3
见附件中的 airport/airport3.ans
提示
【样例解释 #1】
在图中,我们用抵达、离开时刻的数对来代表一架飞机,如 ( 1 , 5 ) (1, 5) (1,5) 表示时刻 1 1 1 抵达、时刻 5 5 5 离开的飞机;用 √ \surd √ 表示该飞机停靠在廊桥,用 × \times × 表示该飞机停靠在远机位。
我们以表格中阴影部分的计算方式为例,说明该表的含义。在这一部分中,国际区有 2 2 2 个廊桥, 4 4 4 架国际航班飞机依如下次序抵达:
- 首先 ( 2 , 11 ) (2, 11) (2,11) 在时刻 2 2 2 抵达,停靠在廊桥。
- 然后 ( 4 , 15 ) (4, 15) (4,15) 在时刻 4 4 4 抵达,停靠在另一个廊桥。
- 接着 ( 7 , 17 ) (7, 17) (7,17) 在时刻 7 7 7 抵达,这时前 2 2 2 架飞机都还没离开、都还占用着廊桥,而国际区只有 2 2 2 个廊桥,所以只能停靠远机位。
- 最后 ( 12 , 16 ) (12, 16) (12,16) 在时刻 12 12 12 抵达,这时 ( 2 , 11 ) (2, 11) (2,11) 这架飞机已经离开,所以有 1 1 1 个空闲的廊桥,该飞机可以停靠在廊桥。
根据表格中的计算结果,当国内区分配 2 2 2 个廊桥、国际区分配 1 1 1 个廊桥时,停靠廊桥的飞机数量最多,一共 7 7 7 架。
【样例解释 #2】
当国内区分配 2 2 2 个廊桥、国际区分配 0 0 0 个廊桥时,停靠廊桥的飞机数量最多,一共 4 4 4 架,即所有的国内航班飞机都能停靠在廊桥。
需要注意的是,本题中廊桥的使用遵循“先到先得”的原则,如果国际区只有 1 1 1 个廊桥,那么将被飞机 ( 1 , 19 ) (1, 19) (1,19) 占用,而不会被 ( 3 , 4 ) (3, 4) (3,4)、 ( 5 , 6 ) (5, 6) (5,6)、 ( 7 , 8 ) (7, 8) (7,8)、 ( 9 , 10 ) (9, 10) (9,10) 这 4 4 4 架飞机先后使用。
【数据范围】
对于
20
%
20 \%
20% 的数据,
n
≤
100
n \le 100
n≤100,
m
1
+
m
2
≤
100
m_1 + m_2 \le 100
m1+m2≤100。
对于
40
%
40 \%
40% 的数据,
n
≤
5000
n \le 5000
n≤5000,
m
1
+
m
2
≤
5000
m_1 + m_2 \le 5000
m1+m2≤5000。
对于
100
%
100 \%
100% 的数据,
1
≤
n
≤
10
5
1 \le n \le {10}^5
1≤n≤105,
m
1
,
m
2
≥
1
m_1, m_2 \ge 1
m1,m2≥1,
m
1
+
m
2
≤
10
5
m_1 + m_2 \le {10}^5
m1+m2≤105,所有
a
1
,
i
,
b
1
,
i
,
a
2
,
i
,
b
2
,
i
a_{1, i}, b_{1, i}, a_{2, i}, b_{2, i}
a1,i,b1,i,a2,i,b2,i 为数值不超过
10
8
{10}^8
108 的互不相同的正整数,且保证对于每个
i
∈
[
1
,
m
1
]
i \in [1, m_1]
i∈[1,m1],都有
a
1
,
i
<
b
1
,
i
a_{1, i} < b_{1, i}
a1,i<b1,i,以及对于每个
i
∈
[
1
,
m
2
]
i \in [1, m_2]
i∈[1,m2],都有
a
2
,
i
<
b
2
,
i
a_{2, i} < b_{2, i}
a2,i<b2,i。
【感谢 hack 数据提供】
附件下载
airport.zip 1.89KB
先有一个关键结论:因为题目要求“先到先得”,所以事先对所有飞机分组后按左端点从大到小排序。
如果给廊桥编上号,每当一架飞机到达后,如果强制让它进入编号最小的廊桥,这样不会影响进入廊桥飞机的集合,且当廊桥总数增加后,原本在哪个廊桥的飞机,仍旧会进入原来的廊桥。
这个性质也很好理解:新加入廊桥可以视为原先的远机位的一部分,进入新加的这个廊桥的飞机可以视为在原来该进入远机位的飞机中贪心选择一轮。
至此本题已经有了优秀的解决方案:处理出每架飞机在廊桥充足的时候会进入哪个廊桥,然后在差分数组上统计一下即可。
考虑如何实现这个预处理过程:
考虑新建一个时间堆和空闲的编号堆,把所有的飞机按照到达时间排序,如果编号堆中有剩余就取出最小的编号,绑上这家飞机的离开时间扔进时间堆中,每当一架飞机进入之前,先把已经超过离开时间的飞机弹出,释放空闲的廊桥。
到这里做法就很清晰了:我们按照开头提到的分配方法来安排航班的停靠位置,记录各廊桥停靠的航班数,做一个前缀和,最后枚举分配给某个区的廊桥数,算出各情况下两区实际使用廊桥的航班数总和,即可解决本题。
时间复杂度 O(n\log n)O(nlogn)。
题目链接
注:详细解释都在代码里
#include<bits/stdc++.h>
using namespace std;
int ans1[100005],ans2[100005],n;
struct node
{
int x,y;//记录到达时间和离开时间
}a[100005],b[100005];
bool cmp(node a,node b)
{
return a.x<b.x;//按到达时间排序
}
struct node1
{
int lk,id;//离开时间和编号
};
bool operator<(node1 a,node1 b)
{
return a.lk>b.lk;//离开时间小的优先级高
}
void fun(node a[],int m,int ans[])
{
int i;
priority_queue<int,vector<int>,greater<int> >q;
priority_queue<node1>p;
for(i=1;i<=n;i++)
q.push(i);//编号入队
for(i=1;i<=m;i++)
{
while(!p.empty()&&a[i].x>=p.top().lk)//判断到达时间是否比离开时间大并且队列不为空
{//必须是while,因为可能还有其他的廊桥可以停靠
q.push(p.top().id);//将编号还给队列
p.pop();//删除
}
if(!q.empty())//如果还有多余的廊桥
{
int id=q.top();//廊桥编号
q.pop();//廊桥数量减一
ans[id]++;//对应廊桥停靠数量加一
p.push({a[i].y,id});//加入新的廊桥
}
}
for(i=1;i<=n;i++)//计算廊桥数量相对应的结果
ans[i]+=ans[i-1];//前缀和
}
int main()
{
int m1,m2,i,ans=0;
cin>>n>>m1>>m2;
for(i=1;i<=m1;i++)
cin>>a[i].x>>a[i].y;//国内
for(i=1;i<=m2;i++)
cin>>b[i].x>>b[i].y;//国际
sort(a+1,a+1+m1,cmp);//排序
sort(b+1,b+1+m2,cmp);//按离开时间的升序排列
fun(a,m1,ans1);//计算国内航班数
fun(b,m2,ans2);//计算国际航班数
for(i=0;i<=n;i++)
ans=max(ans,ans1[i]+ans2[n-i]);//计算最大停靠总数
cout<<ans;//输出最大停靠总数
return 0;
}