吉司机线段树 笔记

本文原在 2025-02-10 11:05 发布于本人洛谷博客。

P6242 【模板】线段树 3(区间最值操作、区间历史最值)

对一个数组,实现以下操作:

  1. 区间加上一个数。
  2. 区间内所有数对一个数取 \(\min\)
  3. 区间求和。
  4. 区间求最大值。
  5. 区间求历史版本最大值。

大体思路

操作 \(1,3,4\) 是简单的。

操作 \(5\) 我们可以维护 tag 的历史最大值,下传时就可以用当前儿子最大值,加上 tag 的历史最大值,维护儿子的历史最大值了。

对于操作 \(2\),同时维护区间内的次大值,当递归到某个区间,使得只有最大值需要取 \(\min\) 时,取 \(\min\) 返回即可,据说这样的复杂度是 \(\log^2\) 的,因此需要对区间最大值进行特殊维护。

实现

1. 建树

定义以下线段树:

struct TREE {
    int s, mx, cnt, mx2, mxh;
    int t1, t2, tm1, tm2;
}

其中:

  • \(s\) 是区间和。
  • \(mx\) 是区间最大值。
  • \(cnt\) 是区间内最大值的数量。
  • \(mx_2\) 是区间内次大值。
  • \(mx_h\) 是区间历史最大值。
  • \(t_1\) 是即将下传给儿子当前最大值的 tag。
  • \(t_2\) 是即将下传给儿子当前其他值的 tag。
  • \(tm_1\) 是即将下传给儿子当前最大值的历史最大 tag。
  • \(tm_2\) 是即将下传给儿子当前其他值的历史最大 tag。

\(t_2,tm_2\) 的意义:当前区间的最大值可能只来自两个儿子区间的其中一个,所以另一个儿子区间不能使用最大值的 tag。

根据定义,push_up 和 build 是容易实现的:

void push_up(int rt) {
    t[rt].s = t[ls].s + t[rs].s; // 求和
    t[rt].mx = max(t[ls].mx, t[rs].mx); // 取最大值
    t[rt].mxh = max(t[ls].mxh, t[rs].mxh); // 取历史最大值
    if (t[ls].mx > t[rs].mx) { // 最大值来自左子树
        t[rt].cnt = t[ls].cnt; // 最大值个数和左子树最大值个数相同
        t[rt].mx2 = max(t[ls].mx2, t[rs].mx); // 次大值为右子树最大值或左子树次大值
    } else if (t[rs].mx > t[ls].mx) { // 最大值来自右子树
        t[rt].cnt = t[rs].cnt; // 同理
        t[rt].mx2 = max(t[rs].mx2, t[ls].mx);
    } else { // 左右子树均出现最大值
        t[rt].cnt = t[ls].cnt + t[rs].cnt; // 最大值个数相加
        t[rt].mx2 = max(t[ls].mx2, t[rs].mx2); // 次大值为左子树次大值或右子树次大值
    }
}
void build(int rt, int l, int r) {
    if (l == r) {
        t[rt].s = t[rt].mx = t[rt].mxh = a[l];
        t[rt].mx2 = -oo;
        t[rt].cnt = 1;
        return;
    }
    int mid = l + r >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
    push_up(rt);
}

2. 更新

(1). 操作 \(1\)

先考虑怎么下传标记。

void modify(int rt, int len, int t1, int t2, int tm1, int tm2) {
    // rt 是被下传的节点,len 是被下传节点的区间长度,其他是父节点的参数
    t[rt].s += t1 * t[rt].cnt + t2 * (len - t[rt].cnt); // 求和要区分最大值和其他值
    t[rt].mxh = max(t[rt].mxh, t[rt].mx + tm1); // 历史最大值等于下传前的最大值加上 tag 的历史最大值
    t[rt].mx += t1; // 当前最大值直接更新
    if (t[rt].mx2 != -oo) // 如果有次大值才更新次大值
        t[rt].mx2 += t2; // 直接更新
    t[rt].tm1 = max(t[rt].tm1, t[rt].t1 + tm1); // 历史最大 tag 值更新同历史最大值
    t[rt].tm2 = max(t[rt].tm2, t[rt].t2 + tm2);
    t[rt].t1 += t1, t[rt].t2 += t2; // tag 直接更新
}
void push_down(int rt, int len) {
    // rt 是当前子树,len 是当前区间长度
    int tmp = max(t[ls].mx, t[rs].mx);
    if (t[ls].mx == tmp) // 最大值来自左子树
        modify(ls, len - len / 2, t[rt].t1, t[rt].t2, t[rt].tm1, t[rt].tm2); // 下传
    else // 最大值不来自左子树
        modify(ls, len - len / 2, t[rt].t2, t[rt].t2, t[rt].tm2, t[rt].tm2); // 左子树的最大值是当前子树的非最大值
    if (t[rs].mx == tmp) // 同理
        modify(rs, len / 2, t[rt].t1, t[rt].t2, t[rt].tm1, t[rt].tm2);
    else
        modify(rs, len / 2, t[rt].t2, t[rt].t2, t[rt].tm2, t[rt].tm2);
    t[rt].t1 = t[rt].t2 = t[rt].tm1 = t[rt].tm2 = 0;
}

于是就可以直接更新了:

void update_add(int rt, int l, int r, int x, int y, int v) {
    if (l > y or r < x)
        return;
    if (x <= l and r <= y) {
        modify(rt, r - l + 1, v, v, v, v);
        return;
    }
    push_down(rt, r - l + 1);
    int mid = l + r >> 1;
    update_add(ls, l, mid, x, y, v);
    update_add(rs, mid + 1, r, x, y, v);
    push_up(rt);
}

(2). 操作 \(2\)

根据前面提到的思路,一直递归直到只有最大值需要被修改再修改。

void update_mn(int rt, int l, int r, int x, int y, int v) {
    if (l > y or r < x or v >= t[rt].mx) // 没有需要修改的
        return;
    if (x <= l and r <= y and t[rt].mx2 < v) { // 满足修改条件
        int tmp = t[rt].mx - v;
        t[rt].s -= tmp * t[rt].cnt; // 区间总和少了
        t[rt].mx = v; // 取 min
        t[rt].t1 -= tmp; // 对区间最大值的 tag 也应减少
        return;
    }
    push_down(rt, r - l + 1);
    int mid = l + r >> 1;
    update_mn(ls, l, mid, x, y, v);
    update_mn(rs, mid + 1, r, x, y, v);
    push_up(rt);
}

3. 查询

这一部分就是直接查了。

int query_s(int rt, int l, int r, int x, int y) {
    if (l > y or r < x)
        return 0;
    if (x <= l and r <= y)
        return t[rt].s;
    push_down(rt, r - l + 1);
    int mid = l + r >> 1;
    return query_s(ls, l, mid, x, y) + query_s(rs, mid + 1, r, x, y);
}
int query_mx(int rt, int l, int r, int x, int y) {
    if (l > y or r < x)
        return -oo;
    if (x <= l and r <= y)
        return t[rt].mx;
    push_down(rt, r - l + 1);
    int mid = l + r >> 1;
    return max(query_mx(ls, l, mid, x, y), query_mx(rs, mid + 1, r, x, y));
}
int query_mxh(int rt, int l, int r, int x, int y) {
    if (l > y or r < x)
        return -oo;
    if (x <= l and r <= y)
        return t[rt].mxh;
    push_down(rt, r - l + 1);
    int mid = l + r >> 1;
    return max(query_mxh(ls, l, mid, x, y), query_mxh(rs, mid + 1, r, x, y));
}
posted @ 2025-02-11 16:25  Garbage_fish  阅读(68)  评论(0)    收藏  举报