UOJ #284. 快乐游戏鸡题解(长链剖分+单调栈合并)
一番战斗之后,程序猿被计算鸡们赶走了。随着垫子计算鸡一声令下:“追!”,于是计算鸡村全村上下开始乘胜追击。计算鸡们希望在新的一年到来之际给程序猿以重创,出掉这一年的恶气。
可是程序猿一追就走,一走就跑,一跑就无影无踪。计算鸡们开始跋山涉水寻找程序猿的踪迹。快乐游戏鸡跟随大部队走着走着,突然说道:“我好像打过类似的游戏”。
快乐游戏鸡玩过的游戏是这样的:给定一棵 n 个结点的树,其中 1 号结点是根。每次玩家可以在树上行走,走过一条边需要 1 秒的时间,但只能往当前所在的点的某个儿子走,不能往父亲走。每次游戏需要从 s 号结点走到 t 号结点去。
玩家有一个总死亡次数,初始为 0。每个结点上有一个程序猿和一个参数 wi,如果走到结点 i 的时候,当前总的死亡次数小于 wi,那么玩家就会立刻死亡并回到起点 s,且该死亡过程不需要时间;如果总死亡次数大于等于 wi,那么玩家就能熟练地对付程序猿从而安然无恙。注意每次游戏时不需要考虑 s 和 t 上的程序猿。
该游戏会进行若干轮,每轮会清空总死亡次数并给出一组新的 s,t。现在请你对于每一轮游戏输出走到 t 所需要的最短时间(单位为秒)。
保证每个询问的 s 可以到达 t。
数据范围
1≤n,q≤3e5,1≤w≤1e9
Solution
本题作如下假设
s=起点,t=终点,path=路径上的点,dep=深度,w=死亡次数,subtree=子树,stk=单调栈,sum=从s→t的总死亡次数
- Hint1 考虑单条路径,则有我们在path上会受到阻碍当且仅当满足如下条件,那么对于这一条路径来说,我们想要维护这个信息是可以用单调栈维护的,只要满足以上条件,那就可以把这个点放入stk。
path[i].dep<path[j].dep&&path[i].w<path[j].w(i<j)
-
Hint2 考虑s为root的子树,那就会有多条由子树出发到叶子节点的path,那我们可以发现,如果有一个点p∈subtree(s)&&p∈/path(s→t),但是stk[i].w<p.w&&stk[i+1].dep>p.dep,那么此时,这个点也是可以放入stk的,所以我们要维护的单调栈应该是
subtree[i].dep<subtree[j].dep&&subtree[i].w<subtree[j].w(i<j)
-
Hint3 考虑如何维护这个stk,如果我们每次到一个点u,然后直接dfs子树获取所有点并尝试放入stk的话,虽然能够维护,但是复杂度不对。考虑dfs的过程,如果我们能在搜到一个叶子节点的时候,把当前的stk存下来,如果回溯到子树根节点时,再把这些stk合并起来,那么如果我们合并方式合理,那就可以优化复杂度,比如用启发式合并可以做到O(nlogn),又由于这个栈是可以以dep为主关键字的,所以可以用长链剖分,再在回溯的过程进行合并,就能做到O(n)(还没想通复杂度为什么是O(n)).
-
Hint4 考虑统计答案
sum=∑(w[i]−w[i−1])∗dep[i](i∈path(s→t))
但我们现在是在一个某一个节点上,当前的stk中存的是由子树中的节点所构成的,但是t不一定在 这个stk中,所以我们需要用一个二分找到当前stk中第一个w≥max(path(s→t).w),我们令 它为pos, 但是如果我们一个一个去求和那么时间复杂度又变得不可接 受,这里我们可以用一个 前缀和或者后缀和来维护这个信息,由于我们使用的是栈,由于先入后出的性质我们只能记后缀 和,这样子每次从栈顶拿出元素,后面的和就不会受到影响。
-
Hint5 考虑求解w≥max(path(s→t).w),我们直接用树上倍增的方式即可求解
-
Hint6 关于栈的空间分配问题,由于每个子树下的栈的空间最大都会达到dep,如果每个都去开一个数组显然空间复杂度会达到O(n2),考虑空间复用。在长链剖分自下往上的回溯过程中,已经被并入长链的轻链是不会再用到的,并且长链上父节点是可以直接继承重儿子的信息的,所以我们只要按照长链剖分的dfn序来进行空间复用就行,我们对每一个节点p可以开一对L[p](栈顶),R[p](栈底)表示当前节点stk的起始位置和结束位置,优先遍历重儿子,因为单调栈长度最大为dep[p],而dfn[p]≥dep[p]所以肯定是空间够用的。
-
Hint7 计算答案时还有很多细节,加在代码的注释中。
Code