CF1651F 题解
CF1651F 题解
题意:
数轴上有 \(n\) 个塔,其中塔 \(i\) 的坐标是 \(i\),魔力值上限是 \(lim_i\),每秒末可以回复 \(re_i\) 点魔力。
还有 \(q\) 个怪物,第 \(i\) 个怪物出现时间为第 \(t_i\) 秒初,血量为 \(h_i\),初始坐标为 \(1\) 并以 \(1m/s\) 速度向右走。
第 \(0\) 秒初时,所有塔的魔力值为 \(lim_i\),怪物经过某塔的瞬间,该塔会耗费魔力值给怪物造成同等伤害,
形式化的,若此时怪物血量为 \(H\),塔此时魔力值为 \(W\),则 \(H\) 和 \(W\) 同时减去 \(\min(H,W)\)。
你要求出,所有经过了最后一个塔的怪物,在经过了最后一个塔后的剩余血量之和。
\(n,q,t_i\le2\times10^5,h_i\le10^{12},\forall i<q,t_i<t_{i+1},\) 其余所有数都在
int
范围内。
做法:
我们注意到,每个怪物的速度都是一样的,由此我们可以得到,数轴上所有怪物的位置顺序永远不变,
即不存在任何两塔 \(i,j\) 以及两怪物 \(x,y\),使得:\(x\) 比 \(y\) 先经过 \(i\),而 \(y\) 比 \(x\) 先经过 \(j\),
故一个自然的想法是,按出现时间的先后枚举每个怪物,并快速维护当前怪物给所有塔带来的影响。
我们发现,怪物给所有塔的魔力值带来的影响,永远是以下形式:
从点 \(1\) 出发,将经过的所有塔的魔力值清零,最后到达杀死该怪物的塔或所有塔的后面。
注意到,在这段路径上,只有最多一个塔的魔力值损耗,与怪物本身的血量有关,
其他的塔的魔力值都会被清零,并按照其自身的永远不变的魔力值恢复速度来恢复魔力值。
我们称那个与怪物血量有关的点为关键点,则其他点就是非关键点。
此时,我们将数轴上的所有塔,用若干个关键点划分成了一些连续段,而任意一连续段,
其对应的区间 \([l,r]\) 中的所有塔,必定被同一怪物 \(c\) 依次清零过,此时我们称该连续段颜色为 \(c\)。
那我们考虑能否快速维护一个连续段的信息。形式化的,若有个怪物 \(i\) 站在了点 \(l\),血量为 \(H\),
且存在点 \(r\) 使区间 \([l,r]\) 是颜色为 \(c\) 的连续段,此时我们能否快速维护,
怪物 \(i\) 最后会在区间 \([l,r]\) 中的哪个位置被杀死,或到达 \(r\) 的右边。
考虑这个问题,我们可以先二分 \(mid\) 并将问题转化为,判定怪物 \(i\) 能否活着经过点 \(mid\)。
而怪物 \(i\) 能活着经过点 \(mid\),等价于区间 \([l,mid]\) 里所有塔对怪物造成的伤害值小于 \(H\),
故我们需要求出区间 \([l,mid]\) 中塔对怪物造成的伤害值总和。
注意到 \([l,mid]\) 是 \([l,r]\) 的子区间,故对 \(\forall j\in[l,mid]\),塔 \(j\) 在第 \(t_c+j-1\) 秒初的魔力值为 \(0\),
原因是 \([l,r]\) 中所有塔的魔力值,都被怪物 \(c\) 清零过,而怪物 \(c\) 在 \(t_c\) 秒初时,坐标为 \(1\),
所以怪物 \(c\) 到达点 \(j\) 的时刻,就是第 \(t_c+j-1\) 秒初。
同理,我们也可以算出,怪物 \(i\) 到达塔 \(j\) 时是第 \(t_i+j-1\) 秒初,
这与怪物 \(c\) 的到达时间相差 \(t_i-t_c\) 秒,即塔 \(j\) 有 \(t_i-t_c\) 秒的时间,从 \(0\) 开始恢复魔力值,
故我们可以算出,塔 \(j\) 在怪物 \(i\) 到达时的魔力值是:\(\min((t_i-t_c)\times re_j,lim_j)\)。
也就是说,区间 \([l,mid]\) 对怪物 \(i\) 造成了总伤害是:\(\sum\limits_{l\le j\le mid}\min((t_i-t_c)\times re_j,lim_j)\),
而我们需要的,就是快速的算出这个值。
看到式子里的 \(\min\) 是不好维护的,我们考虑能否将 \(\min\) 拆开,
即设 \(X_j=\min((t_i-t_c)\times re_j,lim_j)\),我们分类讨论 \(X_j\) 的值究竟等于哪一项。
若 \(X_j=(t_i-t_c)\times re_j\),则说明了 \((t_i-t_c)\times re_j\le lim_j\),即 \(t_i-t_c\le\lceil\frac{lim_j}{re_j}\rceil\),
而若 \(X_j=lim_j\),也就说明了 \((t_i-t_c)\times re_j>lim_j\),即 \(t_i-t_c>\lceil\frac{lim_j}{re_j}\rceil\)。
我们记 \(val_j=\lceil\frac{lim_j}{re_j}\rceil\),那么对于所有满足 \(t_i-t_c\le val_j\) 的 \(j\),\(X_j\) 值都为 \((t_i-t_c)\times re_j\),
相反的,对于所有满足 \(t_i-t_c>val_j\) 的 \(j\),\(X_j\) 值都为 \(lim_j\),
所以,我们可以将我们要求的式子改写,即:
\(\sum\limits_{l\le j\le mid}X_j=(\sum\limits_{l\le j\le mid,val_j\ge t_i-t_c}(t_i-t_c)\times re_j)+(\sum\limits_{l\le j\le mid,val_j<t_i-t_c}lim_j)\)。
注意到后面两项都是典型的二位偏序的形式,故我们可以用主席树做到单次 \(O(\log n)\),
再乘上二分的复杂度,即我们可以用 \(O(\log^2n)\) 的复杂度来维护一个连续段的信息。
那我们继续考虑,当有若干个连续段时的维护方法。
其实,这时我们可以暴力处理每个关键点,并暴力枚举每个连续段,单个连续段内用上面的方法维护,
而这样的时间复杂度也是对的,原因可以用均摊复杂度的思想证明。
我们可以注意到,在这种做法中,枚举的连续段数与怪物实际经过的关键点数同级,
而怪物每经过一个关键点,都会使这个关键点的魔力值清零,即变成非关键点,
而每个怪物只会在其被某个塔杀死时,使这个塔变成一个新的关键点,
也就是说,关键点的总增量是 \(O(q)\) 的,故我们枚举连续段的总次数也就是 \(O(q)\) 的,
那么总复杂度也就是 \(O(n+qlog^2n)\) 的。
当然,如果代码像我一样实现的不是很好,那复杂度可能会变成 \(O((n+q)log^2n)\) 且常数巨大,
就需要卡一下常数才能过。
当然,上面的时间复杂度还可以优化,就是把二分拿到主席树里面去,就可以做到单 \(\log\)。