P11983 [JOIST 2025] 展览会 3 / Exhibition 3
P11983 [JOIST 2025] 展览会 3 / Exhibition 3
感觉还算有迹可循,搜索树并不算大。
因此有的步骤可以钦定自己前面做的是对的,以及猜猜结论。
Part 1
如果 \(a\) 中每个颜色只出现一次,怎么做?
从前往后考虑每个区间,每次尽量找大的颜色填进去。
你考虑一个选颜色的方案合法的充要条件。如果对于每个颜色 \(c\),所有颜色为 \(c\) 的区间的交集不为空,这个方案就是合法的。
为了快速查找最大的 \(c\),自然地想到把每个颜色 \(c\) 的交集放到线段树上。不妨记作 \([L_c,R_c]\)。
还发现 \([L_c,R_c]\) 一定是两两不交的。
扫描所有区间,并维护 \([L_c,R_c]\) 即可做到 \(O(m \log n)\)。
Part 2
如果有的颜色出现多次,怎么做?
依然考虑充要条件。如果对于每个颜色 \(c\),所有颜色为 \(c\) 的区间可以用不超过 \(cnt(c)\) 个点覆盖,这个方案就是合法的。
这个看上去就比 \(Part 1\) 难刻画多了。快速判定、快速查询都不好做了。
首先有个显而易见的事实:如果有一种颜色 \(c\),选择 \(c\) 的区间不超过 \(c\) 个,那么一定可以往颜色 \(c\) 继续加入区间。
然后发现,每个时刻,这样的颜色 \(c\) 只有一个是需要考虑的。
先想想判定。这是一个经典的问题,把所有区间按 \(l\) 排序,从前往后放点,每个点尽量靠右放。最后 check 是否用了超过 \(cnt(c)\) 个点。
你发现另一个显而易见的事实:如果用的点数 \(<cnt(c)\),一定可以往颜色 \(c\) 加区间。
仔细思考,由于只有 \(= cnt(c)\) 是需要精细处理的,考虑找到一个时刻 \(t\),满足如果将 \(t\) 前剩余的所有区间加入进去,恰好需要用 \(cnt(c)\) 个点去覆盖这些区间。
一次 \(check\) 是 \(O(len \log len)\) 的。直接二分这个时刻 \(t\),复杂度会退化到 \(O(m^2 log^2 m)\)。
由于 \(check\) 复杂度跟 \(len\) 相关,可以想到使用 \(\text {finger search}\),先倍增 \(len\),直到使用的点数 \(\geq cnt(c)\) 时再跑二分,复杂度就是 \(O(\sum_i (len_i \log^2 len_i)) = O(m \log^2 m)\)。
Part 3
\(\text{finger search}\) 这块拼图看上去很有道理。
接下来我们还要做这样一件事:
有一些颜色 \(c\),每种颜色都满足 \(\text{mincover}(c) = cnt(c)\),对于一个区间 \([l,r]\),我们要快速查询其能选择的最大颜色 \(c\),或者都不能加入。
如果能 \(polylog(n)\) 做这个,把这个与 \(Part 2\) 结合一下,就可以把整个题目解决了。
先想想如何快速判定 \([l,r]\) 能否加入颜色 \(c\)。我们肯定不能把 \([l,r]\) 放进集合再重新跑一遍判定。
因为 \([l,r]\) 肯定要被某个点覆盖,想到仿照 \(Part 1\),尝试搞出来颜色 \(c\) 的每个点可以出现的位置区间。
你发现,如果正着跑一遍判定,跑出来的就是每个点最靠右的可行位置 \(vr_x\);反过来,反着跑判定,跑出来的就是每个点最靠左的可行位置 \(vl_x\)。
再仔细思考,你发现,对于第 \(k\) 个点,只要它放在 \(vl_k\) 和 \(vr_k\) 之间的一个位置,就存在一种安排其它点的方法,能够覆盖所有区间。
证明:设 \(k\) 的位置为 \(p_k\)。
考虑把 \(i<k\) 的点 \(i\) 都放到 \(vr_i\),发现左端点 \(\leq p_k\) 的区间均被覆盖。
把 \(i>k\) 的点 \(i\) 都放到 \(vl_i\),发现右端点 \(\geq p_k\) 的区间均被覆盖。
于是,在这个方案下,所有区间都被覆盖。
并且,根据假设,当 \(p_k \notin [vl_k,vr_k]\) 时不存在合法方案。
因此,只要 \([l,r]\) 与 \([vl,vr]^{(c)}\) 集合中的某一个区间有交,就说明 \([l,r]\) 可以放入颜色 \(c\);否则就不能放入颜色 \(c\)。
(还有一个小结论:所有 \(c,k\) 的 \([vl^{(c)}_k, vr^{(c)}_k]\) 两两不交。)
所以有了这些观察后,我们自然地想到,仿照 \(Part 1\),将每个 \([vl_k,vr_k]\) 维护出来,并放到线段树上,就可以实现快速查找啦!
现在问题转化成,怎么高效维护每个颜色的 \([vl_k,vr_k]\)。
Part 4
来点更多观察!
现在我们需要做,在某个颜色中加入一个新区间 \([l,r]\) 后更新 \([vl_k,vr_k]\)。
观察到:对于一个区间 \([l,r]\),假如它和不小于两个 \(k\) 的 \([vl_k,vr_k]\) 有交,那么不需要对 \([vl,vr]\) 做任何更新。
证明:假设 \([l,r]\) 同时和 \([vl_k,vr_k]\),\([vl_{k+1},vr_{k+1}]\) 有交。
假设 \(k\) 放在了 \(p_k \in [vl_k,vr_k]\)。发现把 \(i<k\) 全部放到 \(vr_k\)、把 \(i>k\) 全部放到 \(vl_k\) 是合法的方案。
对于 \(k+1\) 同理。
其它位置同样使用该构造即可。证毕。
我们发现,当且仅当 \([l,r]\) 与恰好一个 \([vl_k,vr_k]\) 有交时,需要对序列进行更新。
但是只是简单的把 \([vl_k,vr_k]\) 变成 \([vl_k,vr_k] \cap [l,r]\) 是不够的,因为 \([vl_k,vr_k]\) 的缩小,可能会导致别的 \([vl,vr]\) 产生变化。
一个具体的例子:
有一个颜色 \(c\),\(cnt(c)=2\),它当前的区间有 \([1,3], [3,4], [4,5]\)。
我们有 \([vl_1,vr_1] = [1,3], [vl_2,vr_2] = [4,5]\)。
加入区间 \([1,2]\) 后,\([vl_1,vr_1]\) 更新为 \([1,2]\),但这会同时影响 \([vl_2,vr_2]\) 变成 \([4,4]\)。
但是我们又不能整体再跑一遍,重新构造出新的 \([vl,vr]\) 序列。
那咋办。
尝试只更新所有变化了的 \([vl,vr]\)。
考虑 \(vr_i\) 是如何计算出来的。当 \(vr_{i-1} = x\) 的时候,所有左端点 \(\leq x\) 的区间都被覆盖。
在此基础上,观察到,\(vr_i\) 等于所有满足 \(l > vr_{i-1}\) 的区间的 \(\min r\)。而这个东西是可以利用数据结构,快速计算的。
我们发现,从左到右,第一个变化的 \(vr\) 就是 \(vr_k\),因此这只会影响后面的 \(vr\)。
从 \(k\) 开始,向右逐个计算新的 \(vr_i\) 的值,如果新的 \(vr_i\) 的值和原来相等,就可以停止更新。对于 \(vl\) 也是同理的。
修改 \([vl,vr]\) 序列的同时,要在线段树上面作修改。
我也不知道为什么,但是这个东西均摊好像是对的,听说总共只会修改 \(O(m)\) 次。(反正,写完这个就过了)
最后是,finger search 的部分是 \(O(m \log^2 m)\),维护 \([vl,vr]\) 序列以及线段树的部分是 \(O(m \log m)\),总复杂度是 \(O(m \log^2 m)\) 的。
finger search 套上一些归并排序的手法,应该是可以单 log 的。

浙公网安备 33010602011771号