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\)

代码链接

posted @ 2022-03-14 10:28  GaryH  阅读(68)  评论(0编辑  收藏  举报