算法竞赛进阶指南 0X40数据结构进阶——你能回答这些问题吗

题目链接

AcWing 245. 你能回答这些问题吗 easy

题目描述

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

  • 1 x y,查询区间 [ x , y ] [x,y] [x,y] 中的最大连续子段和。
  • 2 x y,把 A [ x ] A[x] A[x] 改成 y y y

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

输入格式

第一行两个整数 N , M N,M N,M

第二行 N N N 个整数 A [ i ] A[i] A[i]

接下来 M M M 行每行 3 3 3 个整数 k , x , y k,x,y k,x,y k = 1 k=1 k=1 表示查询(此时如果 x > y x>y x>y,请交换 x , y x,y x,y), k = 2 k=2 k=2 表示修改。

输出格式

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

每个答案占一行。

数据范围
  • N ≤ 500000 , M ≤ 100000 N≤500000,M≤100000 N500000,M100000

  • − 1000 ≤ A [ i ] ≤ 1000 −1000≤A[i]≤1000 1000A[i]1000

输入样例:

5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2

输出样例:

2
-1

解法:线段树

由于我们要查询的是 最大连续子段和,所以每一个节点 t [ u ] t[u] t[u] 都维护一个最大子段和 t m a x tmax tmax,它表示在 [ t [ u ] . l , t [ u ] . r ] [t[u].l , t[u].r] [t[u].l,t[u].r] 这个区间内的最大子段和。

我们现在需要考虑的是如何用子节点区间 来更新 父节点区间的最大子段和。

可能有以下几种情况:

1.父节点的最大字段和 就是 左子节点的最大子段和,即 t [ u ] . t m a x = t [ u ∗ 2 ] . t m a x t[u].tmax = t[u * 2] .tmax t[u].tmax=t[u2].tmax
在这里插入图片描述

2.父节点的最大字段和 就是 右子节点的最大子段和,即 t [ u ] . t m a x = t [ u ∗ 2 + 1 ] . t m a x t[u].tmax = t[u * 2 + 1] .tmax t[u].tmax=t[u2+1].tmax

在这里插入图片描述

3.父节点的最大字段和 就是 左子节点的最大后缀和 t [ u ∗ 2 ] . r m a x t[u * 2].rmax t[u2].rmax 和 右子节点的最大前缀和 t [ u ∗ 2 + 1 ] . l m a x t[u * 2 +1].lmax t[u2+1].lmax 之和,即 t [ u ] . t m a x = t [ u ∗ 2 ] . r m a x + t [ u ∗ 2 + 1 ] . l m a x t[u].tmax = t[u*2].rmax + t[u*2+1].lmax t[u].tmax=t[u2].rmax+t[u2+1].lmax。所以每一个节点还需要维护两个 l m a x , r m a x lmax , rmax lmax,rmax 才行。

在这里插入图片描述

对于 l m a x lmax lmax 的更新有两种情况:

1.父节点的最大前缀和 t [ u ] . l m a x t[u].lmax t[u].lmax 就是左子节点的最大前缀和 t [ u ∗ 2 ] . l m a x t[u*2].lmax t[u2].lmax,即 t [ u ] . l m a x = t [ u ∗ 2 ] . l m a x t[u].lmax = t[u * 2].lmax t[u].lmax=t[u2].lmax

在这里插入图片描述
2.父节点的最大前缀和 t [ u ] . l m a x t[u].lmax t[u].lmax 就是左子节点的区间和 s u m sum sum 加上 右子节点的最大前缀和 t [ u ∗ 2 + 1 ] . l m a x t[u*2+1].lmax t[u2+1].lmax,即 t [ u ] . l m a x = s u m + t [ u ∗ 2 + 1 ] . l m a x t[u].lmax = sum + t[u * 2+1].lmax t[u].lmax=sum+t[u2+1].lmax

在这里插入图片描述

对于 r m a x rmax rmax 的更新也是一样的,所以线段树节点还需要维护一个记录区间和的字段 s u m sum sum

线段树节点构造如下:

struct node{
    int l,r; //区间
    int tmax,lmax,rmax,sum; //最大子段和 , 最大前缀和 , 最大后缀和 ,区间和
}t[N * 4];

从下往上传递信息:

定义了一个重载函数,方便我们操作。

void pushup(node &u, node &l, node &r) {
    u.sum = l.sum + r.sum;
    u.lmax = max(l.lmax, l.sum + r.lmax);
    u.rmax = max(r.rmax, r.sum + l.rmax);
    u.tmax = max(l.rmax + r.lmax, max(l.tmax, r.tmax));
}

void pushup(int u) {  
    //用左子节点 , 右子节点的信息 , 更新父节点的信息
    pushup(t[u], t[u << 1], t[u << 1 | 1]);
}

建树:

void build(int u, int l, int r) {
    //记录叶子节点的信息
    if (l == r) t[u] = {l, r, a[r], a[r], a[r], a[r]};
    else {
        t[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        //从下往上传递信息
        pushup(u);
    }
}

单点修改:

void modify(int u,int x,int v){
    //找到对应的叶子节点 , 并且修改
    if(t[u].l == x && t[u].r == x){
        t[u] = {x,x,v,v,v,v};
        return;
    }
    
    int mid = (t[u]. l  + t[u].r) >> 1;
    
    //要修改的点在左子区间
    if(x <= mid) modify(u * 2 , x  , v);
    //要修改的点在右子区间
    else modify(u * 2 + 1 , x , v);
    
    //从下往上传递信息
    pushup(u);
}

区间查询:

node query(int u, int l, int r) {
    //如果 [l,r] 直接覆盖了当前节点的区间 [t[u].l , t[u].r] 那么就直接返回当前节点
    if (t[u].l >= l && t[u].r <= r) return t[u];

    int mid = t[u].l + t[u].r >> 1;
    
    // t[u].l........mid..........t[u].r
    //       l....r
    //[l,r] 在左子区间
    
    if (r <= mid) return query(u << 1, l, r);
    
    // t[u].l........mid..........t[u].r
    //                     l....r
    //[l,r] 在右子区间
    
    else if (l > mid) return query(u << 1 | 1, l, r);
    //[l,r] 左右子区间都有重叠部分
    else {
        auto left = query(u << 1, l, r);
        auto right = query(u << 1 | 1, l, r);
        node res;
        pushup(res, left, right);
        return res;
    }
}

完整代码:

#include<iostream>
using namespace std;

const int N = 5e5+10;

int a[N];

struct node{
    int l,r;
    int tmax,lmax,rmax,sum;
}t[N * 4];

int n , m;

void pushup(node &u, node &l, node &r) {
    u.sum = l.sum + r.sum;
    u.lmax = max(l.lmax, l.sum + r.lmax);
    u.rmax = max(r.rmax, r.sum + l.rmax);
    u.tmax = max(l.rmax + r.lmax, max(l.tmax, r.tmax));
}

void pushup(int u) {
    pushup(t[u], t[u << 1], t[u << 1 | 1]);
}

//建树
void build(int u, int l, int r) {
    if (l == r) t[u] = {l, r, a[r], a[r], a[r], a[r]};
    else {
        t[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

//单点修改
void modify(int u,int x,int v){
    //找到对应的叶子节点 , 并且修改
    if(t[u].l == x && t[u].r == x){
        t[u] = {x,x,v,v,v,v};
        return;
    }
    
    int mid = (t[u]. l  + t[u].r) >> 1;
    
    //要修改的点在左子区间
    if(x <= mid) modify(u * 2 , x  , v);
    //要修改的点在右子区间
    else modify(u * 2 + 1 , x , v);
    
    //从下往上传递信息
    pushup(u);
}

//区间查询
node query(int u, int l, int r) {
    if (t[u].l >= l && t[u].r <= r) return t[u];

    int mid = t[u].l + t[u].r >> 1;
    
    if (r <= mid) return query(u << 1, l, r);
    
    else if (l > mid) return query(u << 1 | 1, l, r);
    else {
        auto left = query(u << 1, l, r);
        auto right = query(u << 1 | 1, l, r);
        node res;
        pushup(res, left, right);
        return res;
    }
}


int main(){
    cin>>n>>m;
    
    for(int i = 1;i <= n;++i) scanf("%d",&a[i]);
    
    build(1,1,n);
    
    int op , x , y;
    while(m--){
        scanf("%d%d%d",&op,&x,&y);
        
        //查询区间
        if(op == 1){
            if(x > y) swap(x,y);
            cout<<query(1,x,y).tmax<<'\n';
        }
        //单点修改
        else{
            modify(1,x,y);
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值