线段树

线段树要领

  • 维护什么,标记什么
  • 区间整体操作后维护的值以及其他标记如何变化

区间最值

给定序列 \(a\)\(q\) 次操作:

  • 区间取 \(\min\)
  • 查询区间和
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define mp make_pair
#define fi first
#define pb push_back
#define se second
#define deb(x) cerr << #x << "=" << (x) << "; "
#define int long long
int n, q, a[1000010];
struct Seg {
    int mx[4000010], cmx[4000010], mmx[4000010], tag[4000010], sum[4000010];
    void pu(int u) {
        sum[u] = sum[u << 1] + sum[u << 1 | 1];
        mx[u] = max(mx[u << 1], mx[u << 1 | 1]);
        cmx[u] = (mx[u << 1] == mx[u] ? cmx[u << 1] : 0) + (mx[u << 1 | 1] == mx[u] ? cmx[u << 1 | 1] : 0);
        mmx[u] = max(mx[u << 1] == mx[u] ? mmx[u << 1] : mx[u << 1], mx[u << 1 | 1] == mx[u] ? mmx[u << 1 | 1] : mx[u << 1 | 1]);
    }
    void mk(int u, int x) {
        mx[u] += x; tag[u] += x; sum[u] += cmx[u] * x;
    }
    void pd(int u) {
        if (mx[u << 1] + tag[u] == mx[u]) mk(u << 1, tag[u]); 
        if (mx[u << 1 | 1] + tag[u] == mx[u]) mk(u << 1 | 1, tag[u]); tag[u] = 0;
    }
    void chkmn(int u, int l, int r, int L, int R, int x) {
        if (R < l || r < L) return;
        if (!(L <= l && r <= R)) {
            pd(u);
            int mid = (l + r) >> 1;
            chkmn(u << 1, l, mid, L, R, x); chkmn(u << 1 | 1, mid + 1, r, L, R, x); pu(u);
        } else {
            if (l == r) {
                mx[u] = min(mx[u], x); tag[u] = 0; sum[u] = mx[u]; mmx[u] = -1e9; return;
            }
            pd(u);
            if (x >= mx[u]) return;
            else if (x > mmx[u]) mk(u, x - mx[u]);
            else {
                int mid = (l + r) >> 1;
                chkmn(u << 1, l, mid, L, R, x); chkmn(u << 1 | 1, mid + 1, r, L, R, x); pu(u);
            }
        }
    }
    int qrysum(int u, int l, int r, int L, int R) {
        if (L <= l && R >= r) return sum[u];
        else if (!(r < L || R < l)) {
            pd(u);
            int mid = (l + r) >> 1;
            return qrysum(u << 1, l, mid, L, R) + qrysum(u << 1 | 1, mid + 1, r, L, R);
        } return 0;
    }
    int qrymx(int u, int l, int r, int L, int R) {
        if (L <= l && R >= r) return mx[u];
        else if (!(r < L || R < l)) {
            pd(u);
            int mid = (l + r) >> 1;
            return max(qrymx(u << 1, l, mid, L, R), qrymx(u << 1 | 1, mid + 1, r, L, R));
        } return -1e9;
    }
    void build(int u, int l, int r) {
        tag[u] = 0;
        if (l == r) sum[u] = mx[u] = a[l], mmx[u] = -1e9, cmx[u] = 1;
        else {
            int mid = (l + r) >> 1;
            build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); pu(u);
        }
    }
} seg;
signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> q;
        for (int i = 1; i <= n; i++) cin >> a[i];
        seg.build(1, 1, n);
        while (q--) {
            int op, l, r, x;
            cin >> op >> l >> r;
            if (op == 0) {
                cin >> x;
                seg.chkmn(1, 1, n, l, r, x);
            } else if (op == 1) {
                cout << seg.qrymx(1, 1, n, l, r) << "\n";
            } else {
                cout << seg.qrysum(1, 1, n, l, r) << "\n";
            }
        }
    } 
    return 0;
} 

区间二最值

给定序列 \(a\)\(q\) 次操作:

  • 区间取 \(\min\)
  • 区间取 \(\max\)
  • 查询区间和

区间最值点积

给定序列 \(a,b\)\(q\) 次操作:

  • 区间加
  • 区间取 \(\min\)
  • 查询区间 \(a\times b\)

通过吉司机线段树,我们把操作转化为形如一个区间最大值 \(+x\) 的操作。

把最值和非最值分开维护,把询问操作转化为四个询问。

posted @ 2023-05-18 10:31  Network_Error  阅读(20)  评论(0)    收藏  举报