加载中...

线段树区间最值、历史最值(吉司机线段树)

区间最值问题可优化的核心在于:区间内数的种类数一定会随着操作不断进行而减少,直到区间内只剩下某一种数,此时就可用懒标记处理批量操作。因此可以考虑用势能分析优化复杂度。

有关线段树势能分析的技巧见这篇博客:blog

无区间加操作时,复杂度为 \(O(n\log n)\)

  • 简略证明:对于线段树的所有结点,共包含 \(O(n\log n)\) 个数,故所有结点内包含数的种类数总和也是 \(O(n\log n)\)。该算法的瓶颈在于修改值 \(x \le mxse\) 时需要继续暴力递归两个左右儿子,但是每向下暴力 \(dfs\) 一次,一定会导致该结点内的数的种类数至少减 \(1\)(至少最大值一定会收敛到次大值,即与次大值合并)。故结合势能分析,暴力向下 \(dfs\) 的总次数不会超过 \(O(n\log n)\)

有区间加操作时,需要额外添加懒标记(对最大值与非最大值的懒更新机制要有所区别),复杂度为 \(O(n\log^{2} n)\)

对区间做 \(\min,\max\) 的操作,本质上转化成了对区间最大值的加操作。

有关更多资料见 CF blog

P6242(模板)

吉司机线段树模板题,涉及区间加、区间更新最值(\(x = min(x, y)\))、区间求和、区间求最值、区间求历史最值。

const int N = 500005;
const ll LOWEST = -4e18; // 无穷小,用于表示不存在次大值

int arr[N];
ll sum[N << 2], maxv[N << 2], maxcnt[N << 2], maxv_se[N << 2], maxHistory[N << 2];
ll maxAdd[N << 2], otherAdd[N << 2], maxAddTop[N << 2], otherAddTop[N << 2];

#define lc p<<1
#define rc p<<1|1

void pushup(int p) {
    sum[p] = sum[lc] + sum[rc];
    maxv[p] = max(maxv[lc], maxv[rc]);
    maxHistory[p] = max(maxHistory[lc], maxHistory[rc]);
    if (maxv[lc] > maxv[rc]){
        maxcnt[p] = maxcnt[lc];
        maxv_se[p] = max(maxv_se[lc], maxv[rc]);
    } 
    else if (maxv[lc] < maxv[rc]){
        maxcnt[p] = maxcnt[rc];
        maxv_se[p] = max(maxv[lc], maxv_se[rc]);
    } 
    else{
        maxcnt[p] = maxcnt[lc] + maxcnt[rc];
        maxv_se[p] = max(maxv_se[lc], maxv_se[rc]);
    }
}

// 修改结点p的信息与懒标记
void lazy(int p, int n, ll maxAddv, ll otherAddv, ll maxUpv, ll otherUpv){ // n 表示结点区间长度
    maxHistory[p] = max(maxHistory[p], maxv[p] + maxUpv);
    maxAddTop[p] = max(maxAddTop[p], maxAdd[p] + maxUpv);
    otherAddTop[p] = max(otherAddTop[p], otherAdd[p] + otherUpv);
    sum[p] += maxAddv * maxcnt[p] + otherAddv * (n - maxcnt[p]);
    maxv[p] += maxAddv;
    if (maxv_se[p] != LOWEST){
        maxv_se[p] += otherAddv;
    }
    maxAdd[p] += maxAddv;
    otherAdd[p] += otherAddv;
}

void pushdown(int p, int ln, int rn){ // ln、rn 分别表示左、右子结点区间长度
    ll tmp = max(maxv[lc], maxv[rc]); // 注意不能直接用 maxv[p],因为可能已经被修改过
    if (maxv[lc] == tmp){
        lazy(lc, ln, maxAdd[p], otherAdd[p], maxAddTop[p], otherAddTop[p]);
    } 
    else{
        lazy(lc, ln, otherAdd[p], otherAdd[p], otherAddTop[p], otherAddTop[p]);
    }
    
    if (maxv[rc] == tmp){
        lazy(rc, rn, maxAdd[p], otherAdd[p], maxAddTop[p], otherAddTop[p]);
    } 
    else{
        lazy(rc, rn, otherAdd[p], otherAdd[p], otherAddTop[p], otherAddTop[p]);
    }
    
    // clear
    maxAdd[p] = otherAdd[p] = maxAddTop[p] = otherAddTop[p] = 0;
}

void build(int p, int l, int r){
    if (l == r){
        sum[p] = maxv[p] = maxHistory[p] = arr[l];
        maxv_se[p] = LOWEST;
        maxcnt[p] = 1;
    } 
    else{
        int mid = l + r >> 1;
        build(lc, l, mid);
        build(rc, mid + 1, r);
        pushup(p);
    }
    maxAdd[p] = otherAdd[p] = maxAddTop[p] = otherAddTop[p] = 0;
}

void add(int p, int nl, int nr, int ql, int qr, ll v){
    if (ql <= nl && nr <= qr){
        lazy(p, nr - nl + 1, v, v, v, v);
        return;
    } 
    int mid = nl + nr >> 1;
    pushdown(p, mid - nl + 1, nr - mid);
    if (ql <= mid) add(lc, nl, mid, ql, qr, v);
    if (qr > mid) add(rc, mid + 1, nr, ql, qr, v);
    pushup(p);
}

void setMin(int p, int nl, int nr, int ql, int qr, ll v){
    if (v >= maxv[p]) return;
    if (ql <= nl && nr <= qr && maxv_se[p] < v){
        lazy(p, nr - nl + 1, v - maxv[p], 0, v - maxv[p], 0);
        return;
    } 
    int mid = nl + nr >> 1;
    pushdown(p, mid - nl + 1, nr - mid);
    if (ql <= mid) setMin(lc, nl, mid, ql, qr, v);
    if (qr > mid) setMin(rc, mid + 1, nr, ql, qr, v);
    pushup(p);
}

ll querySum(int p, int nl, int nr, int ql, int qr){
    if (ql <= nl && nr <= qr){
        return sum[p];
    } 
    int mid = nl + nr >> 1;
    pushdown(p, mid - nl + 1, nr - mid);
    ll res = 0;
    if (ql <= mid) res += querySum(lc, nl, mid, ql, qr);
    if (qr > mid)  res += querySum(rc, mid + 1, nr, ql, qr);
    return res;
}

ll queryMax(int p, int nl, int nr, int ql, int qr){
    if (ql <= nl && nr <= qr){
        return maxv[p];
    }
    int mid = nl + nr >> 1;
    pushdown(p, mid - nl + 1, nr - mid);
    ll res = LOWEST;
    if (ql <= mid) res = max(res, queryMax(lc, nl, mid, ql, qr));
    if (qr > mid)  res = max(res, queryMax(rc, mid + 1, nr, ql, qr));
    return res;
}

ll queryHistoryMax(int p, int nl, int nr, int ql, int qr){
    if (ql <= nl && nr <= qr){
        return maxHistory[p];
    }
    int mid = nl + nr >> 1;
    pushdown(p, mid - nl + 1, nr - mid);
    ll res = LOWEST;
    if (ql <= mid) res = max(res, queryHistoryMax(lc, nl, mid, ql, qr));
    if (qr > mid)  res = max(res, queryHistoryMax(rc, mid + 1, nr, ql, qr));
    return res;
}

void solve() 
{
    int n, m;
    cin >> n >> m;
    
    for (int i = 1; i <= n; i ++) {
        cin >> arr[i];
    }
    
    build(1, 1, n);
    
    int op, ql, qr;
    ll v;
    for (int i = 1; i <= m; i++) {
        cin >> op >> ql >> qr;
        if (op == 1) {
            cin >> v;
            add(1, 1, n, ql, qr, v);
        } 
        else if (op == 2) {
            cin >> v;
            setMin(1, 1, n, ql, qr, v);
        } 
        else if (op == 3) {
            cout << querySum(1, 1, n, ql, qr) << "\n";
        } 
        else if (op == 4) {
            cout << queryMax(1, 1, n, ql, qr) << "\n";
        } 
        else {
            cout << queryHistoryMax(1, 1, n, ql, qr) << "\n";
        }
    }
}

CF1290E

code

CF997E

code

2020ICPC南京J

涉及到区间最值修改、查询区间异或和、查询区间内某个二进制位上 \(1\) 的个数。由于吉司机线段树最值修改的本质是只对区间最大/最小值进行修改,可以看作被修改的值只有一种,因此对于绝大多数信息,都可以很方便地维护。具体实现思路见官解。

tutorial

code

posted @ 2026-06-20 17:39  小橘奏  阅读(12)  评论(0)    收藏  举报