P7913 [CSP-S 2021] 廊桥分配 题解
首先我们需要对原来形象的实际问题进行抽象化。
以样例 1 的国内航班抵达、离开时刻信息为例,我们可以通过区间来表示每架飞机位于机场的时间:
不妨先假设廊桥数量没有限制,那么当一个新的飞机抵达时,显然需要使用一个空闲的廊桥。当飞机离开时,就可以释放出一个空闲的廊桥。因此,我们可以记下所需廊桥的数量。当一架飞机抵达时计数器 $+1$,离开时 $-1$:
具体流程如下:
时刻 | 航班序号 | 抵达/离开 | 当前正在使用的廊桥数量(计数器) |
---|---|---|---|
$1$ | $1$ | 抵达 | $0+1=1$ |
$3$ | $2$ | 抵达 | $1+1=2$ |
$5$ | $1$ | 离开 | $2-1=1$ |
$6$ | $3$ | 抵达 | $1+1=2$ |
$8$ | $2$ | 离开 | $2-1=1$ |
$9$ | $4$ | 抵达 | $1+1=2$ |
$10$ | $3$ | 离开 | $2-1=1$ |
$13$ | $5$ | 抵达 | $1+1=2$ |
$14$ | $4$ | 离开 | $2-1=1$ |
$18$ | $5$ | 离开 | $1-1=0$ |
显然,所需的廊桥数量为计数器在所有时刻内的最大值,在本例中为 $2$。
接下来考虑增加廊桥限制时的操作方法:
在进行计数器 $+1$ 操作时,如果计数器在操作之后超过廊桥限制数量,那么就让该飞机不进入廊桥,同时进行标记以防后面操作时对该航班进行 $-1$ 操作。
如果廊桥数量限制为 $1$,那么示意图将会变成这样(灰色表示无法进入廊桥):
具体实现方法是先把读入的航班信息按照抵达时刻从小到大进行排序并对时刻进行离散化。然后在 $[0,n]$ 内枚举廊桥数量限制,并在限制条件下对每个离散化后的时刻进行 $+1/-1$ 处理,依次得到最大航班数量。最后进行统计即可:
1 |
|
这样,我们便得到了一个 $\mathcal O(n(m_1+m_2)+m_1 \log m_1+m_2 \log m_2) \approx \mathcal O(n^2)$ 的做法,可以获得 $40$ 分的好成绩。
接着考虑如何对 $O(n^2)$ 做法进行优化。
原来的暴力程序采用先枚举限制再遍历时刻的方法。由于遍历时刻的操作不可优化,因此我们可以考虑先遍历时刻再枚举限制。
不妨用数组 ${\text{sum}_i}$ 来记录限制数量为 $i$ 时当前所需廊桥的数量,用数组 ${\text{ans}_i}$ 来记录限制数量为 $i$ 时当前可进入廊桥的航班数量。不难发现,这两个数组一定单调不递减。
在遍历时刻的过程中,对于每一个时刻,我们可以分两种情况讨论:
该时刻有飞机抵达。此时我们可以在 ${\text{sum}_i}$ 中进行二分,找到一个最大的 $p$,使得 $\text{sum}_p \lt p$(即飞机还可进入廊桥)。紧接着,我们把 $[p,n]$ 内的 $\text{sum}$ 值和 $\text{ans}$ 值全部加上 $1$。和暴力程序类似,我们在该航班的对应位置标记 $p$,方便后续操作。如果所有的 $\text{sum}$ 值都不小于 $p$,那么就标记 $-1$。
该时刻有飞机离开。由于该飞机的离开时刻一定晚于抵达时刻,因此我们可以查看之前所做的标记。如果标记 $\text{pos}$ 不为 $-1$,那么就把 $[\text{pos},n]$ 内的 $\text{sum}$ 全部减 $1$,完成还原操作。
如果每次进行暴力 $+1$ / $-1$,那么时间复杂度仍为 $\mathcal O(n^2)$。但考虑到修改都是区间性的,因此可以通过线段树、区修单查的树状数组或分块实现。
常数较大的 $\mathcal O(n \log^2n)$ 线段树代码:(最大时限 $\approx 200$ $\text{ms}$)
1 |
|
$\mathcal O(n \log n + n \sqrt n)$ 的分块代码:(最大时限 $\approx 100$ $\text{ms}$)
1 |
|
常数较小的 $\mathcal O(n \log^2 n)$ 树状数组代码:(最大时限 $\approx 50$ $\text{ms}$)
1 |
|
P7913 [CSP-S 2021] 廊桥分配 题解