一栋大楼中 (10^5) 个人紧急疏散。大楼可以被视作网格图(正视图),有三个区域,由两个宽为 1 的楼道隔开。走出楼道最下面即为离开。同一时刻一个位置只能有一个人。左右区域的人不能互相串。每个人每一秒能移动一格。问所有人离开所需的最小时间
这题正解比暴力好写(一般贪心题都这样),因此考场上没想到贪心就很难受。
首先考虑中间没有人的情况。这是个经典套路了。我们可以把每个人单独的花费时间算出来,然后如果花费时间相同,则它们会冲突,需要让一个人的时间 + 1;如果还出现相同,就继续 + 1,直到没有相同的为止。这个可以用并查集来维护。
然后考虑中间有人的情况。如果人非常少,那么我们可以暴力枚举每个人去左边还是去右边。
现在考虑一般情况。考虑到这道题实际上是要求“最晚走的人的走的时间最小”,于是考虑二分答案,保证每个人的时间都在 (mid) 以内即可。我们设 (L_i) 表示第 (i) 个人去左边的时间,(R_i) 表示第 (i) 个人去右边的时间。然后我们将所有人按照 (R_i) 从大到小排序,如果能去左边就尽量去左边,否则去右边
感性证明:显然时间小更优,那么以后的所有人对于“去右边”都比当前这个人优。如果能够把当前这个人安排到左边,那么就算以后有个人也想安排到左边被占了,他还可以来右边,并且肯定比当前这个人的选择更多。
看来,“想不到怎么贪心就排个序”的方法还是挺有用的。
另外,此题的离散化也需要一些小技巧。暴力的 (n^2) 离散化显然不行,直接用 (map) 也感觉多个 (log) 不太稳(或许也可以手写 unordered_map)。考虑到有用的值只可能是计算出来的时间或者计算出来的时间 + 一些人数。我们可以把最劣情况模拟一遍,搞出所有的有用时间点,并且中间还需要空一些格子。显然,这样的时间点数量不会超过 (4n)。不太好描述,直接看代码吧:
inline void lsh() {
sort(h + 1, h + 1 + htot);
int nw = 0;
for (register int i = 1; i <= htot; ++i) {
if (h[i] <= nw + 1) ++nw, lh[++ltot] = nw;
else ++nw, lh[++ltot] = nw, nw = h[i], lh[++ltot] = nw;
}
for (register int i = 1; i <= n; ++i)
nd[i].t1 = lower_bound(lh + 1, lh + 1 + ltot, nd[i].t1) - lh,
nd[i].t2 = lower_bound(lh + 1, lh + 1 + ltot, nd[i].t2) - lh;
}