[线段树] AcWing-245 你能回答这些问题吗

245. 你能回答这些问题吗

题目

        给定长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

                1. 1 x y ,查询区间 \left [ x , y\right ] 中的最大连续子段和,即  \max_{x\leq l\leq r\leq y} {\sum_{i=l}^{r}A[i]} 。

                2. 2 x y , 把 A\left [ x \right ] 改成 y 。

         对于每个查询指令,输出一个整数表示答案。

思路

        这道题要求的是 区间查询 和 单点修改,每一次查询都是求最大连续区间和,鉴于我是在《算法竞赛进阶指南》的线段树篇看到的这个题目,所以我们使用 线段树 来求解这个问题。线段树使我们能够动态维护区间信息,但是我们依然需要想办法来让各个区间的最大连续区间和可以合并起来。

        我们使用正常的线段树建树方法来 Build 这个线段树,同时再维护4个信息:区间和 sum,区间最大连续子段和 val,从左端点开始的最大连续子段和 lsum,从右端点开始的最大连续子段和 rsum。

        在节点p是叶节点时,它的 sum, val, lsum, rsum 都相等,否则这个节点p的最大连续子段(val)和是下面三个值中最大的:节点p的左子树的最大连续子段和,节点p的右子树的最大连续子段和,节点p的左子树的最大连续右子段和(rsum)+右子树的连续最大左子段和(lsum)。

        在建立线段树时,我们需维护每个节点的区间和(sum),最大左子段和(lsum) 为这个节点的左子树的最大左子段和 或者 左子树的区间和(sum)和右子树的最大左子段和。最大右子段和(rsum)同理。

        查询线段树时,将符合条件的节点拿出,以是否在查询区间中 同时 是否能增大部分和为依据取舍答案节点,最后获得最终答案。

代码

#include <bits/stdc++.h>
using namespace std;

#define INF 0x3F3F3F3F
#define MAXN 5+500000

int n, m;
int a[MAXN];

struct Mem {
    int l, r;
    int sum, lsum, rsum, val;
} mem[MAXN*4];

void UpdateSegtree (int p) {
    mem[p].sum = mem[p*2].sum + mem[p*2+1].sum; //单纯区间和
    mem[p].lsum = max(mem[p*2].lsum, (mem[p*2].sum+mem[p*2+1].lsum)); //与最左端连接的部分的最大连续子段和
    mem[p].rsum = max((mem[p*2].rsum+mem[p*2+1].sum), mem[p*2+1].rsum); //与最右端连接的部分的最大连续子段和
    mem[p].val = max(max(mem[p*2].val, mem[p*2+1].val), (mem[p*2].rsum+mem[p*2+1].lsum)); //最大连续子段和:仅来自于一边,或者来自于左右两边的连接
}

void BuildSegtree (int p, int l, int r) { //建立线段树
    mem[p].l = l; mem[p].r = r;
    if (mem[p].l == mem[p].r) {
        mem[p].val = mem[p].sum = mem[p].rsum = mem[p].lsum = a[l];
        return;
    }
    int mid = (l+r)/2;
    BuildSegtree(p*2, l, mid);
    BuildSegtree(p*2+1, mid+1, r);
    UpdateSegtree(p);
}

Mem QuerySegtree (int p, int l, int r) {
    if (l<=mem[p].l && mem[p].r<=r) { return mem[p]; } //查询到这一次层时,如果包含了一个区域,那么这个区域严格包含在答案区间中,是答案的一部分
    Mem ans, tr, tl; //建立3个节点:ans为本层的答案节点,tr为右子树,tl为左子树
    tr.lsum = tr.rsum = tr.sum = tr.val = tl.lsum = tl.rsum = tl.sum = tl.val = -INF;
    ans.sum = 0;
    ans.lsum = ans.rsum = -INF;
    int mid = (mem[p].l+mem[p].r)/2;
    if (l <= mid) { //如果左子树包含在查询区间内
        tl = QuerySegtree(p*2, l, r);
        ans.sum += tl.sum; //ans需要向上传递,所以区间和需要计算
    }
    if (mid < r) {
        tr = QuerySegtree(p*2+1, l, r);
        ans.sum += tr.sum;
    }
    ans.val = max(max(tr.val, tl.val), (tl.rsum+tr.lsum));
    if (l <= mid) ans.lsum = max(tl.lsum, (tl.sum+tr.lsum)); //如果左子树包含在查询区间内,那么本层答案中的lsum就可以有两种选项
    else if (l > mid) ans.lsum = max(ans.lsum, tr.lsum); //否则只能是右子树的左最大子段和
    if (mid < r) ans.rsum = max(tr.rsum, (tr.sum+tl.rsum));
    else if (mid >= r) ans.rsum = max(ans.rsum, tl.rsum);
    return ans;
}

void PchangeSegtree (int p, int x, int v) { //单点修改
    if (mem[p].l == mem[p].r) {
        mem[p].val = mem[p].sum = mem[p].rsum = mem[p].lsum = v;
        return;
    }
    int mid = (mem[p].l+mem[p].r)/2;
    if (x <= mid) PchangeSegtree(p*2, x, v);
    else PchangeSegtree(p*2+1, x, v);
    UpdateSegtree(p); //注意维护性质
}

int main () {
    while (scanf("%d %d", &n, &m) != EOF) {
        for (int i=1; i<=n; i++) {
            scanf("%d", &a[i]);
        }
        BuildSegtree(1, 1, n);
        for (int i=0; i<m; i++) {
            int inp, l, r;
            scanf("%d %d %d", &inp, &l, &r);
            if (inp == 1) {
                if (l > r) swap(l, r);
                printf("%d\n", QuerySegtree(1, l, r).val);
            }
            else PchangeSegtree(1, l, r);
        }
        
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值