吉司机线段树

吉司机线段树模版题即【模版】线段树 3

食用前请先学习线段树基础。

\(\text{Description}\)

给定 \(n, T\),表示有一个长度为 \(n\) 的序列 \(a\),我们需要维护 \(a, b\) 数组,题目给定 \(a\)

\(T\) 次修改或查询,对于每一次操作,按照以下格式给出:

  • 1 l r k:对于所有的 \(i \in [l, r]\),将 \(A_i\) 加上 \(k\)
  • 2 l r v:对于所有的 \(i \in [l, r]\),将 \(A_i\) 变成 \(\min(a_i, v)\)
  • 3 l r:求 \(\sum_{i = l}^{r} a_i\)
  • 4 l r:对于所有的 \(i \in [l, r]\),求 \(a_i\) 的最大值。
  • 5 l r:对于所有的 \(i \in [l, r]\),求 \(b_i\) 的最大值。

每一次操作后,令所有的 \(i \in [l, r]\)\(b_{i} \leftarrow \max(b_{i}, a_{i})\)

\(\text{Limitations}\)

  • \(1 \le n, m \le 5 \times 10^5\)
  • \(-5 \times 10^8 \le a_i \le 5 \times 10^8\)
  • \(1 \le l \le r \le n\)
  • \(|k| \le 2000\)
  • \(-5 \times 10^8 \le v \le 5 \times 10^8\)
  • \(opt \in [1, 5]\)

\(\text{Solution}\)

\(\text{Analysis}\)

操作 \(1, 3\)【模版】线段树 1,在此不多赘述。

操作 \(4\) 也为普通线段树模版,每个节点多维护一个 \(mx\) 表示该区间的最大值,update 函数改为求左右儿子的 \(\max\),更新数组值时无需 \(\times size\),询问改为求 \(\max\) 即可。

接下来尝试处理操作 \(5, 2\)

这两个操作需要我们维护 \(a_{i}\) 的历史最值和区间最值修改,操作 \(5\) 的难点在于 \(b\) 的修改。

我们注意到,当 \(tag\) 最大时下传才可以让儿子最大,而只有儿子最大才有可能更新历史最大。设这个 \(tag\)\(tag\),再设一个 \(tag2\) 表示没下传前的最大 \(tag\),下传更新可以借助 \(tag2\) 解决。

接下来就是难点操作 \(2\)

操作 \(2\) 的暴力时空复杂度可达 \(\mathcal O(n)\),我们不妨换一种思考方式,如果只有一种数 \(> v\),那么我们只需要一个标记即可解决,如果有两种数,那么先化为一种,再解决,所以,操作 \(2\) 的解决方案和递归相似,为实现这个操作,我们要维护三个变量,\(mx, smx, cnt\),分别表示最大值,严格次大值以及最大值数量,在之前的讨论中,只有一种数 \(> v\) 时可以快速更新,故判断能否快速更新的方法即为判断 \(mx > k > smx\),最大值数量在之后的更新需使用。

\(\text{Code}\)

建议复制到 Dev C++ / VS Code 等文本编辑器查看。

时空复杂度分析,建树 \(\mathcal O(n)\),之后普通线段树部分显然 \(\mathcal O(\log_2 n)\),之后吉司机线段树的部分我不知道,AI 告诉我均摊是 \(\mathcal O(\log_2 n)\),所以总复杂度就是 \(\mathcal O((n + m) \log_2 n)\)

#define ll long long
const int N = 5e5 + 5; int n, T; ll a[N];
struct Segment_Tree {
    struct Node {
    // 对应:最大值,历史最大值,严格次大值,区间总和,最大值出现次数,区间长度
        ll mx, hmx, smx, sum; int cnt, size;
        struct Tag {
        // 最大值懒标记,非最大值懒标记,历史最大值懒标记,历史非最大值懒标记。
            ll add = 0, lmx = 0, hadd = 0, hlmx = 0;
        } tag;
    } g[N << 2];
    inline void update(int idx) { //依据儿子修改自己的值
        int l = idx * 2, r = idx * 2 + 1;
        // sum 相加即可,最大值直接求最大值即可
        g[idx].sum = g[l].sum + g[r].sum, g[idx].mx = max(g[l].mx, g[r].mx), g[idx].hmx = max(g[l].hmx, g[r].hmx);
        // 最大值数量根据儿子节点哪边最大决定,严格次大值同样
        if (g[l].mx == g[r].mx) // 相等时,最大值数量即为两边儿子的最大值数量和,严格次大值即为两个儿子的严格次大值的最大值
            g[idx].cnt = g[l].cnt + g[r].cnt, g[idx].smx = max(g[l].smx, g[r].smx);
        else if (g[l].mx > g[r].mx) // 左儿子最大时,最大值数量即为左儿子的最大值数量,严格次大值即为左儿子的严格次大值与右儿子的最大值的最大值
            g[idx].cnt = g[l].cnt, g[idx].smx = max(g[l].smx, g[r].mx);
        else if (g[l].mx < g[r].mx) // 同上
            g[idx].cnt = g[r].cnt, g[idx].smx = max(g[l].mx, g[r].smx);
    }
    inline void build(int idx, int l, int r) { // 建图函数
        g[idx].size = (r - l) + 1; // 计算 size
        if (l == r) { // 如果到达叶子节点,设初值,因为叶子节点没有严格次大值,故设为 inf
            g[idx].mx = g[idx].hmx = a[l], g[idx].smx = -1e18, g[idx].cnt = 1, g[idx].sum = a[l];
            return;
        }
        int mid = l + r >> 1; // 左右递归
        build(idx * 2, l, mid), build(idx * 2 + 1, mid + 1, r);
        update(idx); //更新自己
    }
    inline void update_add(int idx, ll val) { // 操作 1 更新的封装函数
        // 总和加区间长度 * val,最大值 + val,历史最大值求最大值与自己的 max
        g[idx].sum += g[idx].size * val, g[idx].mx += val, g[idx].hmx = max(g[idx].hmx, g[idx].mx);
        if (g[idx].smx != -1e18) // 有严格次大值时,严格次大值 + val
            g[idx].smx += val;
        // 接下来是 Tag,最大值和非最大值 + val,历史最大的标记求对应 Tag 与自己的 max
        g[idx].tag.add += val, g[idx].tag.lmx += val, g[idx].tag.hadd = max(g[idx].tag.hadd, g[idx].tag.add), g[idx].tag.hlmx = max(g[idx].tag.hlmx, g[idx].tag.lmx);
    }
    inline void update_min(int idx, ll val) { // 操作 2 更新的封装函数
        if (g[idx].mx <= val) //如果 mx 比 val 小那么这个操作 2 没用
            return;
        ll t = g[idx].mx - val;
    // 存一下最大值与 val 的差,sum 减这个差乘最大值数量,最大值设为 val,Tag 部分只有最大值 Tag 更新,需减 t,历史最大值和非最大值不用更新也不该更新,更新后和没更新一样
        g[idx].sum -= t * g[idx].cnt, g[idx].mx = val, g[idx].tag.add -= t;
    }
    inline void pushdown(int idx) { // 标记下传,这里写的有点一坨,压行可能压过头了(?
        int l = idx * 2, r = idx * 2 + 1;
        ll maxv = max(g[l].mx, g[r].mx); // maxv 区分哪个更大,分类讨论 l, r 的两种不同情况
        if (g[l].mx == maxv) { // 左边更大 / 左右一样大,处理是一样的,所以不重要,因为 l 是最大值,所以 sum 等的更新使用最大值 Tag
        // 更新各参数,l.sum += i.最大值 Tag * l.最大值数量再加上 i.非最大值 Tag * l.非最大值数量,l.历史最大值更新为 l.最大值与 l.当前最大值加 i.历史最大 Tag 求 max,l.最大值加上 i.当前 Tag,总之就是历史的用历史更新,其他的用当前的更新
            g[l].sum += g[idx].tag.add * g[l].cnt + g[idx].tag.lmx * (g[l].size - g[l].cnt), g[l].hmx = max(g[l].hmx, g[l].mx + g[idx].tag.hadd), g[l].mx += g[idx].tag.add;
            if (g[l].smx != -1e18) // 有严格次大值时,严格次大值 + 非最大值标记
                g[l].smx += g[idx].tag.lmx;
        // l.历史最大值 Tag 设为自己与 l.最大值 Tag 加上 i.历史最大值 Tag,l.历史非最大值 Tag 设为自己与 l.当前非最大值 Tag + i.历史非最大值最大 Tag,l.当前 Tag 正常与普通线段树相同
            g[l].tag.hadd = max(g[l].tag.hadd, g[l].tag.add + g[idx].tag.hadd), g[l].tag.hlmx = max(g[l].tag.hlmx, g[l].tag.lmx + g[idx].tag.hlmx), g[l].tag.add += g[idx].tag.add, g[l].tag.lmx += g[idx].tag.lmx;
        } else { // l 不是最大值,所以 sum 等的更新使用非最大值 Tag,其他同上
            g[l].sum += g[idx].tag.lmx * g[l].size, g[l].hmx = max(g[l].hmx, g[l].mx + g[idx].tag.hlmx), g[l].mx += g[idx].tag.lmx;
            if (g[l].smx != -1e18)
                g[l].smx += g[idx].tag.lmx;
            g[l].tag.hadd = max(g[l].tag.hadd, g[l].tag.add + g[idx].tag.hlmx), g[l].tag.hlmx = max(g[l].tag.hlmx, g[l].tag.lmx + g[idx].tag.hlmx), g[l].tag.add += g[idx].tag.lmx, g[l].tag.lmx += g[idx].tag.lmx;
        }
        if (g[r].mx == maxv) { // r 的更新同上
            g[r].sum += g[idx].tag.add * g[r].cnt + g[idx].tag.lmx * (g[r].size - g[r].cnt), g[r].hmx = max(g[r].hmx, g[r].mx + g[idx].tag.hadd), g[r].mx += g[idx].tag.add;
            if (g[r].smx != -1e18) g[r].smx += g[idx].tag.lmx;
            g[r].tag.hadd = max(g[r].tag.hadd, g[r].tag.add + g[idx].tag.hadd), g[r].tag.hlmx = max(g[r].tag.hlmx, g[r].tag.lmx + g[idx].tag.hlmx), g[r].tag.add += g[idx].tag.add, g[r].tag.lmx += g[idx].tag.lmx;
        } else {
            g[r].sum += g[idx].tag.lmx * g[r].size, g[r].hmx = max(g[r].hmx, g[r].mx + g[idx].tag.hlmx), g[r].mx += g[idx].tag.lmx;
            if (g[r].smx != -1e18) g[r].smx += g[idx].tag.lmx;
            g[r].tag.hadd = max(g[r].tag.hadd, g[r].tag.add + g[idx].tag.hlmx), g[r].tag.hlmx = max(g[r].tag.hlmx, g[r].tag.lmx + g[idx].tag.hlmx), g[r].tag.add += g[idx].tag.lmx, g[r].tag.lmx += g[idx].tag.lmx;
        }
        g[idx].tag = {0, 0, 0, 0}; // 标记下传结束,将自己的 Tag 清空
    }
    inline void modify_add(int idx, int l, int r, int ql, int qr, ll val) { // 操作 1 的 modify 函数
        int mid = l + r >> 1;
        if (ql <= l && r <= qr) { // 如果已经包含该区间,用封装函数更新即可
            update_add(idx, val);
            return;
        }
        pushdown(idx); // 这部分的 modify 和 query 大多都是板子改了一点点,不做讲解
        if (qr <= mid)
            modify_add(idx * 2, l, mid, ql, qr, val);
        else if (ql > mid)
            modify_add(idx * 2 + 1, mid + 1, r, ql, qr, val);
        else
            modify_add(idx * 2, l, mid, ql, qr, val), modify_add(idx * 2 + 1, mid + 1, r, ql, qr, val);
        update(idx);
    }
    inline void modify_min(int idx, int l, int r, int ql, int qr, ll val) { // 操作 2 的 modify 函数
        int mid = l + r >> 1;
        if (val >= g[idx].mx) // val 大于等于 mx,无意义操作
            return;
        if (ql <= l && r <= qr && g[idx].smx < val) { // 可以快速更新,那么更新
            update_min(idx, val);
            return;
        }
        pushdown(idx);
        if (qr <= mid)
            modify_min(idx * 2, l, mid, ql, qr, val);
        else if (ql > mid)
            modify_min(idx * 2 + 1, mid + 1, r, ql, qr, val);
        else
            modify_min(idx * 2, l, mid, ql, qr, val), modify_min(idx * 2 + 1, mid + 1, r, ql, qr, val);
        update(idx);
    }
    inline ll query_sum(int idx, int l, int r, int ql, int qr) { // 操作 3 的 query 函数
        int mid = l + r >> 1;
        if (ql <= l && r <= qr)
            return g[idx].sum;
        pushdown(idx);
        if (qr <= mid)
            return query_sum(idx * 2, l, mid, ql, qr);
        else if (ql > mid)
            return query_sum(idx * 2 + 1, mid + 1, r, ql, qr);
        else
            return query_sum(idx * 2, l, mid, ql, qr) + query_sum(idx * 2 + 1, mid + 1, r, ql, qr);
    }
    inline ll query_max(int idx, int l, int r, int ql, int qr) { // 操作 4 的 query 函数
        int mid = l + r >> 1;
        if (ql <= l && r <= qr)
            return g[idx].mx;
        pushdown(idx);
        if (qr <= mid)
            return query_max(idx * 2, l, mid, ql, qr);
        else if (ql > mid)
            return query_max(idx * 2 + 1, mid + 1, r, ql, qr);
        else
            return max(query_max(idx * 2, l, mid, ql, qr), query_max(idx * 2 + 1, mid + 1, r, ql, qr));
    }
    inline ll query_hmx(int idx, int l, int r, int ql, int qr) { // 操作 5 的 query 函数
        int mid = l + r >> 1;
        if (ql <= l && r <= qr)
            return g[idx].hmx;
        pushdown(idx);
        if (qr <= mid)
            return query_hmx(idx * 2, l, mid, ql, qr);
        else if (ql > mid)
            return query_hmx(idx * 2 + 1, mid + 1, r, ql, qr);
        else
            return max(query_hmx(idx * 2, l, mid, ql, qr), query_hmx(idx * 2 + 1, mid + 1, r, ql, qr));
    }
} segment;
posted @ 2026-08-21 21:10  __Ydch__(  阅读(1)  评论(0)    收藏  举报