牛客周赛 Round 51 小红的数组
题目链接:
正经题意:
区间询问题,给定
n
n
n个数
a
i
a_i
ai。
q
q
q次询问,每次询问给定区间
[
l
,
r
]
[l,r]
[l,r],询问区间连续子段和的绝对值最大是多少,即存在
x
,
y
x,y
x,y使
l
≤
x
≤
y
≤
r
l\leq x\leq y \leq r
l≤x≤y≤r,求最大的
a
b
s
(
a
[
x
]
+
a
[
x
+
1
]
+
⋯
+
a
[
y
]
)
abs(a[x]+a[x+1]+\dots+a[y])
abs(a[x]+a[x+1]+⋯+a[y])是夺少
简而言之:
就是区间询问题,维护好我们要的东西
思路:
这里有两种思路:
这里都使用线段树实现
1.直接维护区间连续子段最大值,最小值
这样的话绝对值最大就是最大值和最小值绝对值取max即可
这里可以参考y总讲的这个题目->回答这些问题吗?
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 6e6+26;
LL c[N];
struct Node{
LL l,r,sum;
LL tsumx,lsumx,rsumx,tsumi,lsumi,rsumi;
}tr[N*4];
LL a[N];
LL n,m;
void pushup(Node & u,Node & l,Node & r){
u.sum = l.sum + r.sum;
u.lsumx = max(l.lsumx,l.sum+r.lsumx);//从左边开始的连续子段
u.rsumx = max(r.rsumx,r.sum+l.rsumx);//从右边开始的连续子段
u.tsumx = max(l.tsumx,r.tsumx);//当前区间连续子段最大值
u.tsumx = max(u.tsumx,l.rsumx+r.lsumx);
//--------以下同理
u.lsumi = min(l.lsumi,l.sum+r.lsumi);
u.rsumi = min(r.rsumi,r.sum+l.rsumi);
u.tsumi = min(l.tsumi,r.tsumi);
u.tsumi = min(u.tsumi,l.rsumi+r.lsumi);
}
void pushup(int u){
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
if(l==r){
tr[u] = {l,r,c[r],c[r],c[r],c[r],c[r],c[r],c[r]};
}else{
tr[u] = {l,r};
int mid = l + r >> 1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
pushup(u);
}
}
Node query(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u];
int mid = tr[u].l + tr[u].r >> 1;
if(r <= mid){
return query(u<<1,l,r);
}else if(l > mid){
return query(u<<1|1,l,r);
}else{
Node ans;
Node node1 = query(u<<1,l,r);
Node node2 = query(u<<1|1,l,r);
pushup(ans,node1,node2);
return ans;
}
}
int main(){
LL n,q;
scanf("%lld",&n);
for(int i = 1;i<=n;i++){
scanf("%lld",&c[i]);
}
scanf("%lld",&q);
build(1,1,n);
while(q--){
LL l,r;
scanf("%lld%lld",&l,&r);
Node ans = query(1,l,r);
printf("%lld\n",max(ans.tsumx,-ans.tsumi));
}
}
2.可以容易看到,区间连续子段绝对值最大,其实就是这个区间前缀和最大值减去最小值
首先,前缀和相减一定是连续子段,最大值减最小值保证该连续子段是该区间绝对值最大,
这个思路的实现要容易很多
所以在这个线段树里我们维护最大值,最小值;
注意查找的时候要查
(
l
−
1
,
r
)
(l-1,r)
(l−1,r),因为
s
u
m
[
l
]
sum[l]
sum[l]已经包含了当前区间最左边这个点,意味着左边这个点一定会被计算到,这是不对的,所以要
l
−
−
l--
l−−
还有就是如果有l=1的情况,无法计算到sum[0]=0的值,所以我们直接把区间整体右移,这样就没有边界问题了
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 6e6+26;
LL c[N];
struct Node{
LL l,r;
LL sum;
LL min,max;
}tr[N*4];
LL a[N];
LL n,m;
void pushup(Node & u,Node & l,Node & r){
u.sum = l.sum + r.sum;
u.min = min(l.min,r.min);//前缀和最小
u.max = max(l.max,r.max);//前缀和最大
}
void pushup(int u){
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
if(l == r){
tr[u].sum = a[r];
tr[u].min = a[r];
tr[u].max = a[r];
tr[u].l = l;
tr[u].r = r;
}
else{
tr[u].l = l;
tr[u].r = r;
int mid = l + r>>1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
pushup(u);
}
}
Node query(int u,int l,int r){
if(l <= tr[u].l && r >= tr[u].r) return tr[u];
int mid = tr[u].l + tr[u].r >> 1;
if(r <= mid){
return query(u<<1,l,r);
}else if(l > mid){
return query(u<<1|1,l,r);
}else{
Node ans;
Node node1 = query(u<<1,l,r);
Node node2 = query(u<<1|1,l,r);
pushup(ans,node1,node2);
return ans;
}
}
int main(){
LL n,q;
scanf("%lld",&n);
for(int i = 1;i<=n;i++){
scanf("%lld",&c[i]);
a[i] = a[i-1] + c[i];//前缀和
}
for(int i = n+1;i>=2;i--){
a[i] = a[i-1];
}
a[1] = 0;
//整体右移
scanf("%lld",&q);
build(1,1,n+1);
//因为右移了,所以建树右边界扩大1
while(q--){
LL l,r;
scanf("%lld%lld",&l,&r);
r++;
Node ans = query(1,l,r);
printf("%lld\n",ans.max-ans.min);
}
}