线段树(3)
作者:互联网
例题
2.
Can you answer on these queries III
题 目 简 述 题目简述 题目简述
给定长度为N的数列A,以及M条指令 ( N ≤ 500000 , M ≤ 100000 ) (N≤500000, M≤100000) (N≤500000,M≤100000),每条指令可能是以下两种之一:
“2 x y”,把 A[x] 改成 y。
“1 x y”,查询区间 [x,y] 中的最大连续子段和,即区间 [ l , r ] [l,r] [l,r]内,连续累加和最大的。 对于每个询问,输出一个整数表示答案。
//input
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2
//output
2
-1
思
路
思路
思路
线段树的整体思路不变,我们只需在建树和单点修改时变化一下从下往上传递的信息即可,多维护三个信息,即连续最大子段和dat,左端的连续最大子段和lmax,以及右端的连续最大子段和rmax
t[bh].sum=t[bh*2].sum+t[bh*2+1].sum;
t[bh].lmax=max(t[bh*2].lmax,t[bh*2+1].lmax+t[2*bh].sum);
t[bh].rmax=max(t[bh*2+1].rmax,t[bh*2].rmax+t[2*bh+1].sum);
t[bh].dat=max(t[bh*2].dat,max(t[bh*2+1].dat,t[bh*2].rmax+t[bh*2+1].lmax));
PS:1.这里建议写一个更新函数update(int bh),每次更新值时调用次函数即可
2.线段树一般有多组输入输出,建议用快读或
s
c
a
n
f
,
p
r
i
n
t
f
scanf,printf
scanf,printf。
void update(int bh)
{
t[bh].sum=t[bh*2].sum+t[bh*2+1].sum;
t[bh].lmax=max(t[bh*2].lmax,t[bh*2+1].lmax+t[2*bh].sum);
t[bh].rmax=max(t[bh*2+1].rmax,t[bh*2].rmax+t[2*bh+1].sum);
t[bh].dat=max(t[bh*2].dat,max(t[bh*2+1].dat,t[bh*2].rmax+t[bh*2+1].lmax));
}
正 解 代 码 正解代码 正解代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=500086;
struct tree{
int l,r,lmax,sum,rmax,dat;
}t[4*MAXN];
int n,a[MAXN],m;
void update(int bh)
{
t[bh].sum=t[bh*2].sum+t[bh*2+1].sum;
t[bh].lmax=max(t[bh*2].lmax,t[bh*2+1].lmax+t[2*bh].sum);
t[bh].rmax=max(t[bh*2+1].rmax,t[bh*2].rmax+t[2*bh+1].sum);
t[bh].dat=max(t[bh*2].dat,max(t[bh*2+1].dat,t[bh*2].rmax+t[bh*2+1].lmax));
}
void build(int bh,int l,int r)
{
t[bh].l=l;
t[bh].r=r;
if(l==r)
{
t[bh].sum=a[l];
t[bh].dat=a[l];
t[bh].lmax=a[l];
t[bh].rmax=a[l];
return ;
}
int mid=(l+r)/2;
build(2*bh,l,mid);
build(2*bh+1,mid+1,r);
update(bh);
}
void change(int bh,int x,int summ)
{
if(t[bh].l==t[bh].r)
{
t[bh].sum=summ;
t[bh].dat=summ;
t[bh].lmax=summ;
t[bh].rmax=summ;
return ;
}
int mid=(t[bh].l+t[bh].r)/2;
if(x<=mid) change(bh*2,x,summ);
else change(bh*2+1,x,summ);
update(bh);
}
tree ask(int bh,int l,int r)
{
if(l<=t[bh].l&&r>=t[bh].r)
{
return t[bh];
}
int mid=(t[bh].l+t[bh].r)/2;
int w=-MAXN*10;
if(mid>=r) return ask(bh*2,l,r);
if(mid<l) return ask(bh*2+1,l,r);
else {
tree ans,left,right;
left=ask(bh*2,l,r);
right=ask(bh*2+1,l,r);
ans.sum=left.sum+right.sum;
ans.dat=max(max(left.dat,left.rmax+right.lmax),right.dat);
ans.lmax=max(left.lmax,left.sum+right.lmax);
ans.rmax=max(right.rmax,right.sum+left.rmax);
return ans;
}
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
build(1,1,n);
int a,b,c;
while(m--)
{
scanf("%d%d%d",&a,&b,&c);
if(a==2) change(1,b,c);
else
{
if(b>c) swap(b,c);
printf("%d\n",ask(1,b,c).dat);
}
}
return 0;
}
相信同志们经过以上的训练已经对线段树有了足够的认识。下面让我们进一步了解它。
延迟标记/懒标记(lazy tag)
在进行修改操作时,无论是单点修改,还是区间修改,都需要找到完全包含修改区间
[
q
l
,
q
r
]
[ql,qr]
[ql,qr]的节点
x
x
x并对其本身及其子树中的所有节点进行更新,但如果在查询答案时,答案与修改操作完全无关,那么用来进行修改的时间就会被浪费(血亏),所以我们在执行修改指令时,可以增加一个标记,表示“此节点已经被修改,但其子节点未被修改”,在后面的查询中,我们再检查此节点是否具有标记,有则更新此节点的值并将标记传递给它的子节点,然后清除标记。
下面以区间最大值和区间和为例
void spread(int p)
{
if(t[p].add)
{
t[p*2].maxx+=t[p].add;
t[p*2+1].maxx+=t[p].add;
t[p*2].add+=t[p].add;
t[p*2+1].add+=t[p].add;
t[p].add=0;
}
}
复杂度
这样的情况下,对任意节点的修改都延迟到了“在后续查询操作中进入父节点”,每条查询或修改指令的时间复杂度都被降低到了
O
(
l
o
g
N
)
O(logN)
O(logN)。
标签:int,bh,线段,rmax,dat,lmax,sum 来源: https://blog.csdn.net/zero_orez6/article/details/115263410