题解:P6794 [SNOI2020] 水池

总的来说,思想还是比较简单,但是代码就有点恶心了。

我们规定 \(a_i\) 为此时此刻 \(i\) 这个位置的水的高度,原来的 \(h_i\) 为现在的 \(h_{i+1}\),因为最左边和最右边的隔板为无限高,所以令 \(h_0 = h_{n+1} = +\infty\)

\(2, 3\) 操作都是平凡的,我们来看剩下的两个:

  • 0 x h:思考一下发现就是找到从当前位置开始往左往右的第一个大于等于 \(h\)\(h_l\)\(h_r\),然后对于 \(i \in \left[\ l,r \right]\)\(a_i \leftarrow \max(a_i, h)\)

  • 1 x:若当前位置 \(i < x\),令 \(suf\)\(\max_{i \leq j \leq x}h_j\)\(a_i \leftarrow \min(a_i, suf)\)。同样的,若当前位置 \(i > x\),令 \(pre\)\(\max_{x \leq j \leq i}h_j\)\(a_i \leftarrow \min(a_i, pre)\)

同时需要可持久化。

因为我们发现区间覆盖的标记和排水操作的标记满足广义矩阵乘法,所以可以用线段树维护,\(2\) 操作就是单点修,\(3\) 就是单点查,\(0\) 操作就是在线段树上二分出 \(l, r\),然后进行区间覆盖。

好像对于其他有一些题解的可持久化线段树是直接复制两个子节点然后进行 pushdown 操作,但是这样空间会比较炸,其实可以建操作树的。

接下来着重讲一下 \(1\) 操作如何打标记,\(1\) 操作有从左边排水和从右边排水,我们以右边排水为例。

由于是和后缀取最大值的形式,我们想到了单边递归线段树的手法,例如,先递归右边然后更新完再返回最大值,这样就获得了左边节点的后缀最大值,然后接着递归左节点即可,这里给出代码示例。

int upd(int p, int l, int r, int x, int c){
    if(x >= r){
        d[p].rtag = min(d[p].rtag, c); //rtag是指右边排水的标记
        return max(c, d[p].lh); //lh是指左侧挡板最大的
    }
    pushdown(p);
    int mid = (l + r) >> 1;
    if(x <= mid) return upd(ls[p], l, mid, x, c);
    return upd(ls[p], l, mid, x, upd(x, mid + 1, r, c, rs[p]));
}

最终询问的答案即为 \(\min(rtag,ltag,a_i)\)

代码可以参考其他题解因为我写的太丑了,如有疑问或错误请私信我更正,谢谢。

posted @ 2026-06-06 12:48  Super_lollipop  阅读(9)  评论(0)    收藏  举报