题目链接
题目描述
给定长度为 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 N≤500000,M≤100000
-
− 1000 ≤ A [ i ] ≤ 1000 −1000≤A[i]≤1000 −1000≤A[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[u∗2].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[u∗2+1].tmax。
3.父节点的最大字段和 就是 左子节点的最大后缀和 t [ u ∗ 2 ] . r m a x t[u * 2].rmax t[u∗2].rmax 和 右子节点的最大前缀和 t [ u ∗ 2 + 1 ] . l m a x t[u * 2 +1].lmax t[u∗2+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[u∗2].rmax+t[u∗2+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[u∗2].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[u∗2].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[u∗2+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[u∗2+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;
}