洛谷P6792 [SNOI2020] 区间和 题解 吉司机线段树

题目链接:https://www.luogu.com.cn/problem/P6792

这题可以用维护线性函数转折点的势能线段树解决。

普通的“最小值、次小值、最小值个数”还不够:即使修改值没有达到次小值,最大子段对应的区间也可能发生变化。

思路

1. 把维护的信息看成线性函数

线段树维护:

  • sum:区间和;
  • pre:最大前缀和;
  • suf:最大后缀和;
  • ans:最大子段和。

后三者允许取空区间。

设当前节点的最小值为 \(mn\)。如果只把最小值增加 \(d\),其他元素不变,那么一个固定子区间的和会增加

\[d\times cnt, \]

其中 \(cnt\) 是这个子区间中最小值的个数。

因此,对于上述四项信息,除了当前值,还维护它对应的最小值个数。

但这些信息只能在“最优区间不发生变化”时直接更新,所以还需要维护一个阈值 lim:

当 \(mn\le x<lim\) 时,可以直接按照记录的最小值个数更新。

2. 计算转折点

最大前缀和、最大后缀和、最大子段和的合并公式分别是:

\[pre=\max(pre_l,sum_l+pre_r), \]

\[suf=\max(suf_r,suf_l+sum_r), \]

\[ans=\max(ans_l,ans_r,suf_l+pre_r). \]

它们都是若干线性函数的最大值。

假设当前胜出的函数为

\[v_1+c_1d, \]

另一个候选为

\[v_2+c_2d, \]

其中 \(v_1\ge v_2\)。

  • 若 \(c_2\le c_1\),后者不会反超。
  • 若 \(c_2>c_1\),后者第一次严格反超时,整数增量为

\[d=\left\lfloor\frac{v_1-v_2}{c_2-c_1}\right\rfloor+1. \]

于是用 \(mn+d\) 更新 lim。

同时,lim 还要受到子节点有效范围的限制:

  • 子节点最小值等于当前最小值,继承其 lim;
  • 否则,不能直接修改到该子节点的最小值,因为此时最小值的组成会改变。

这样也包含了普通线段树 Beats 中次小值的限制。

3. 修改和查询

修改时:

  • 若 \(x\le mn\),直接返回;
  • 若完整覆盖且 \(x<lim\),直接更新四个值;
  • 否则下传并递归,最后重新合并。

查询按照普通最大子段和线段树处理,单次复杂度为 \(O(\log n)\),空间复杂度为 \(O(n)\)。

样例的四次查询分别得到 6、7、10、11。

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 1e5 + 5, inf = 2e9;

struct Line {
    ll v;
    int cnt;

    bool operator<(const Line &a) const {
        return v == a.v ? cnt < a.cnt : v < a.v;
    }
};

Line operator+(const Line &a, const Line &b) {
    return {a.v + b.v, a.cnt + b.cnt};
}

struct Node {
    Line sum, pre, suf, ans;
    int mn, lim;
} tr[maxn << 2];

struct Info {
    ll sum, pre, suf, ans;
};

int n, q, a[maxn];

void check(Node &t, Line a, Line b) {
    if (a.cnt >= b.cnt) return;
    ll d = b.cnt - a.cnt, v = a.v - b.v;
    if (d * (t.lim - (ll)t.mn - 1) <= v) return;
    t.lim = min((ll)t.lim, t.mn + v / d + 1);
}

void push_up(int u) {
    Node &t = tr[u];
    Node a = tr[u << 1], b = tr[u << 1 | 1];

    t.mn = min(a.mn, b.mn);
    t.lim = min(a.mn == t.mn ? a.lim : a.mn,
                b.mn == t.mn ? b.lim : b.mn);

    if (a.mn != t.mn)
        a.sum.cnt = a.pre.cnt = a.suf.cnt = a.ans.cnt = 0;
    if (b.mn != t.mn)
        b.sum.cnt = b.pre.cnt = b.suf.cnt = b.ans.cnt = 0;

    Line pre = a.sum + b.pre;
    Line suf = a.suf + b.sum;
    Line ans = a.suf + b.pre;

    t.sum = a.sum + b.sum;
    t.pre = max(a.pre, pre);
    t.suf = max(b.suf, suf);
    t.ans = max(max(a.ans, b.ans), ans);

    check(t, t.pre, a.pre);
    check(t, t.pre, pre);
    check(t, t.suf, b.suf);
    check(t, t.suf, suf);
    check(t, t.ans, a.ans);
    check(t, t.ans, b.ans);
    check(t, t.ans, ans);
}

void set_val(int u, int x) {
    tr[u].mn = x;
    tr[u].lim = x < 0 ? 0 : inf;
    tr[u].sum = {x, 1};
    tr[u].pre = tr[u].suf = tr[u].ans = {max(0, x), x >= 0};
}

void change(int u, int x) {
    ll d = (ll)x - tr[u].mn;
    tr[u].sum.v += d * tr[u].sum.cnt;
    tr[u].pre.v += d * tr[u].pre.cnt;
    tr[u].suf.v += d * tr[u].suf.cnt;
    tr[u].ans.v += d * tr[u].ans.cnt;
    tr[u].mn = x;
}

void push_down(int u) {
    int x = tr[u].mn;
    if (tr[u << 1].mn < x) change(u << 1, x);
    if (tr[u << 1 | 1].mn < x) change(u << 1 | 1, x);
}

void build(int u, int l, int r) {
    if (l == r) {
        set_val(u, a[l]);
        return;
    }
    int mid = (l + r) / 2;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    push_up(u);
}

void modify(int u, int l, int r, int ql, int qr, int x) {
    if (tr[u].mn >= x) return;
    if (l == r) {
        set_val(u, x);
        return;
    }
    if (ql <= l && r <= qr && x < tr[u].lim) {
        change(u, x);
        return;
    }

    push_down(u);
    int mid = (l + r) / 2;
    if (ql <= mid) modify(u << 1, l, mid, ql, qr, x);
    if (qr > mid) modify(u << 1 | 1, mid + 1, r, ql, qr, x);
    push_up(u);
}

Info merge(Info a, Info b) {
    return {
        a.sum + b.sum,
        max(a.pre, a.sum + b.pre),
        max(b.suf, a.suf + b.sum),
        max(max(a.ans, b.ans), a.suf + b.pre)
    };
}

Info query(int u, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr) {
        return {
            tr[u].sum.v,
            tr[u].pre.v,
            tr[u].suf.v,
            tr[u].ans.v
        };
    }

    push_down(u);
    int mid = (l + r) / 2;
    if (qr <= mid) return query(u << 1, l, mid, ql, qr);
    if (ql > mid) return query(u << 1 | 1, mid + 1, r, ql, qr);
    return merge(query(u << 1, l, mid, ql, qr),
                 query(u << 1 | 1, mid + 1, r, ql, qr));
}

int main() {
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i++) scanf("%d", a + i);
    build(1, 1, n);

    for (int i = 1, op, l, r, x; i <= q; i++) {
        scanf("%d%d%d", &op, &l, &r);
        if (op == 0) {
            scanf("%d", &x);
            modify(1, 1, n, l, r, x);
        }
        else {
            printf("%lld\n", query(1, 1, n, l, r).ans);
        }
    }
    return 0;
}
posted @ 2026-10-09 19:22  quanjun  阅读(5)  评论(0)    收藏  举报