首页 > 技术文章 > UOJ191口胡

lmpp 2022-02-06 21:17 原文

UOJ191,你失败的原因只有一个:你没有强制在线。

首先这个序列末位加加减减很烦,于是换成操作树,这样就变成查询链的信息了。

注意到一个向量 \((x_1,y_1)\)\((x_2,y_2)\) 优秀的条件是 \(x_1*B-y_1*A>x_2*B-y_2*A\),也就是 \((x_1-x_2)*B>(y_1-y_2)*A\)\(\frac B A<\frac {y_1-y_2}{x_1-x_2}\)

于是树剖。

容易发现询问是由链上若干个前缀与一个区间组成的。于是我们似乎只需要实现一个可持久化分块,然后在上面二分即可。

但是最后那个区间怎么办?别急,我们后面再进行讨论。

如果直接使用平衡树复杂度应该是十分优秀的 \(O(m\log^2n)\),加上平衡树的大常数能过就有鬼了。

考虑进行神秘优化。

将对前缀的询问拆下来,然后提前对每条链建立好凸包,凸包应该包含的信息有坐标和在树上的深度。然后离线对斜率排序。

对斜率排序的实现如果不精细是 \(O(m\log^2n)\) 的,考虑先把所有询问向量排序,然后对斜率排序就变为对下标排序,此时使用桶排序即可。

每次询问之前将凸包的指针向后跳(相当于预处理处理二分的位置),因为每个数最多被跳一次所以是 \(O(n)\) 的。

每次询问凸包时需要询问凸包上的“前驱”(前面第一个没被标记的)和“后继”(类似前者),将查询的位置与查询的值再次离线下来。

现在的问题变为查询序列上前面第一个比自身小的值与后面第一个比自身小的值。

将序列的元素从大到小排序,每扫到一个元素就将自己与前面的元素合并,然后前面第一个比自身小的值相当于前面第一个块的最后一个元素,后继同理。

使用并查集即可做到 \(O(m\log n\alpha(n))\)。每条链内部的排序使用桶排序即可保证复杂度。

实际上建立凸包的时候要删点,导致最优决策可能已经被 gank 了。

于是考虑把对序列建立凸包改成对一棵树的每个节点到根的路径建立凸包,问题也就变为查询深度最大的祖先并且满足权值小于某个值。

并查集依旧可以解决这个问题,并且也存在 \(O(n+m)\) 的严格线性树上并查集。

但是这样应该如何对斜率排序呢?

考虑到对斜率排序之后遍历的顺序相当于对这棵树进行 BFS,所以我们每“遍历”到一个节点就对其打上标记,查询变为查询最深的被标记的祖先。

老样子时光倒流,变为查询所在连通块最浅的节点的父亲,仍然可以 \(O((n+m)\alpha(n))\)\(O(n+m)\)

但是你一共有 \(O(m\log n)\) 个询问啊?

对询问分块,分成 \(\log n\) 块,每一块只有 \(O(m)\) 个询问,这样子离线就可以接受了。

但是别忘了还有一些区间。但是数量已经降低到 \(O(m)\) 个了。

前面的无法故技重施,原因是找不到对应的节点。

于是考虑对这条链进行猫树分治,做一个前缀凸壳与后缀凸壳。分治的复杂度为 \(O(n\log n)\),查询因为只需要查询一个前缀一个后缀所以直接二分,复杂度 \(O(m\log n)\),空间复杂度 \(O(n+m)\)

最终复杂度 \(O(n\log n+m\log n\alpha(n))\)\(O((n+m)\log n)\),空间复杂度 \(O(n+m)\),可以通过此题。

因为代码太难写了,所以先鸽子了,回头来补(

推荐阅读