P7913 [CSP-S 2021] 廊桥分配

8 篇文章 0 订阅

[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 架国际航班飞机依如下次序抵达:

  1. 首先 ( 2 , 11 ) (2, 11) (2,11) 在时刻 2 2 2 抵达,停靠在廊桥。
  2. 然后 ( 4 , 15 ) (4, 15) (4,15) 在时刻 4 4 4 抵达,停靠在另一个廊桥。
  3. 接着 ( 7 , 17 ) (7, 17) (7,17) 在时刻 7 7 7 抵达,这时前 2 2 2 架飞机都还没离开、都还占用着廊桥,而国际区只有 2 2 2 个廊桥,所以只能停靠远机位。
  4. 最后 ( 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 n100 m 1 + m 2 ≤ 100 m_1 + m_2 \le 100 m1+m2100
对于 40 % 40 \% 40% 的数据, n ≤ 5000 n \le 5000 n5000 m 1 + m 2 ≤ 5000 m_1 + m_2 \le 5000 m1+m25000
对于 100 % 100 \% 100% 的数据, 1 ≤ n ≤ 10 5 1 \le n \le {10}^5 1n105 m 1 , m 2 ≥ 1 m_1, m_2 \ge 1 m1,m21 m 1 + m 2 ≤ 10 5 m_1 + m_2 \le {10}^5 m1+m2105,所有 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;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
廊桥分配问题是指给定一座长度为n的廊桥以及m个人,每个人需要跨过廊桥到对面。廊桥每次只能让两个人同时通过,且只有两个人的速度加和不超过廊桥长度时才能通过。每个人过桥所需的时间不同,要求找到一种过桥方案使得所有人的总过桥时间最短。 该问题可以通过使用线段树的解法。首先,将n个位置看作是一棵树,每个节点对应一个位置。然后,我们将所有人按照过桥时间从小到大排序,并按照排序结果为每个节点分配一个排序编号。接下来,从左到右遍历排序后的人员列表,对于每个人,我们找到其对应的节点,并为该节点分配一个值,表示该位置可以被占用。 这样,在分配完所有人的节点后,我们得到了一个线段树,每个非叶子节点表示一个廊桥位置,叶子节点表示一个人,其父节点的值表示桥上人员的速度加和。通过遍历这颗树,可以计算出所有人过桥的最短总时间。 具体操作如下: 1. 根据所有人的过桥时间从小到大排序。 2. 为每个节点分配排序编号。 3. 初始化线段树的所有节点为空(未占用)。 4. 从左到右遍历排序后的人员列表,对于每个人: a. 找到对应的节点。 b. 判断该节点是否为空,如果为空,表示该位置可以被占用,否则找到该节点的兄弟节点(该节点的父节点的其他子节点)。 c. 将该节点或其兄弟节点标记为占用,并更新父节点的值。 5. 遍历线段树,计算所有人过桥的总时间。 使用线段树解决廊桥分配问题的时间复杂度为O(nlogn),因为排序的时间复杂度为O(nlogn),遍历人员列表的时间复杂度为O(n),遍历线段树的时间复杂度为O(nlogn)。总的空间复杂度为O(n)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值