【题解】P5609 | 线段树 分段函数维护 人类智慧

· · 题解

对数据结构的爱,未曾衰减?

本题解参考了 Saliеri 的题解。

首先对于这类数经过操作求最终值的问题,可以比较简单地想到一个分块做法:

将序列分块,每块长度为 O(\sqrt n),对于每一块我们需要维护某个值进去会以另外某个值出来,然后就可以在在整块上快速查询然后模拟散块,考虑这个某个值进去会以另外某个值出来的函数,令其为 f(x),容易发现 f(x) 是一个分段函数且每段是一次函数,其段数是 O(\text{区间长度}) 的,考虑对于每个块求出这个分段函数。

维护该分段函数的分界点 k_i 进入该区间后出来且至少减去了 ip 的最小值。

引理:k_i 单增且 k_i -k_{i-1}\geq p

证明:因为 k_i 是最小的满足至少减去了 ip 的值,考虑减去 ip 后一定变成了 0 或负数,至少需要加上 p 才会能够继续减。

考虑增量维护 \{k_i\},新的 k_i 发生改变仅当加上新加入的这个 a_x 后可以减去 p,即 k'_{i+1} 可以被如下更新:

需要满足能够在前面部分减去 ip 并加上 a_x 后大于等于 p,故需要满足如下条件:

k'_{i+1}\gets \min(k_{i+1},\max(k_i,p - a_x+ip))

且需要满足条件 p-a_x+ip < k_{i+1},该条件意味着 能够满足在这次能被减去的数不能在之前被减去了超过 i 次,否则到该数时的值就不是 A-ip+a_x 了,可能会减去更多。

所以我们就有了个 O(n\sqrt n+m\sqrt n\log n) 的做法,但是不太能过,应该怎么继续下去找到一个 \text{polylog} 的做法?

之所以要分块是因为这个分段函数看上去很不可多元合并,只可以单元添加,如果可以多元合并就可以建出线段树,因为本题中的分段函数性质很特殊,考虑设计如下一种合并方式:

枚举在左侧和右侧的被减去的次数 kl_i,kr_j,试图判断能否在整个区间构成减去 k_{i+j} 次的情况,记录左侧的和为 S,则有如下更新:

类似上面,若满足条件 kr_j - s + ip < kl_{i+1} 才可以去更新,即在左侧被减去了 i 次,否则会减去 i+1 次或更多。

对于满足条件的 i,j 有如下更新:

k_{i+j}\gets \min(k_{i+j},\max(kl_i,kr_j-s+ip))

直接枚举 i,j 并进行合并的复杂度是平方的,我们继续挖掘性质。

V(i,j)=\max(kl_i,kr_j-s+ip)

定理:对于一个固定的 i+j,被 i 最小的 i+j 更新到是最优的。

证明:试比较 V(i,j),V(i+1,j-1),因为有 kr_j - s + ip < kl_{i+1},所以一定有 \max(kl_i,kr_j-s+ip) < \max(kl_{i+1},kr_{j-1 }-s+ip)

所以我们只需要对每个 i + j 求出最小的 i 可以去更新它,又因为可以更新的条件是 kr_j - s < kl_{i+1} - ip,随着 i 增大而越来越松,所以用双指针枚举最大的 j 即可。

于是我们求出了所有线段上的分段函数的形态,查询时直接在线段树上依次对 O(\log n) 个分段函数进行二分,时间复杂度 O(n\log n + m \log^2 n)