线段树是一种基于分治思想的二叉树,每一个结点都对应一个区间,叶子节点的区间L=R,非叶子结点,左孩子区间为**[L,(L+R)/2],右孩子区间为[(L+R)/2+1,R].所以和树状数组相比,线段树能更好的维护一个区间= =这也是其优势所在
那么对于每一个结点,我们可以维护什么信息呢?
区间的最值,区间和等等,具体根据题目的要求来确定;
以下代码和示例均维护区间最值(最大值);
以下是一些常用的操作,代码块里均有实现方式
图源@bilibili 算法训练营
看到这可能就有疑问了,线段树的最大作用不是维护一个区间吗,这里除了区间查询并没有区间更新的操作。那么假设我们要对某个区间[L,R]上的所有元素都加减某个元素v,如果按照点更新的方式,毫无疑问时间复杂度为nlogn,这个时间复杂度是很不理想的,于是下面引入懒标记(Lazy_tag)**;
对于某个区间操作,因为后面的操作不一定会涉及这个区间的子区间,所以我们不妨懒一点,既然没有查询,那么这次区间操作我并没有必要更新所有的子区间,我只更新最大的那个,并且标记一下,代表这个区间是进行过操作的就行,等到下次操作要用到这个区间我们再对子区间进行更新。这样的话,区间更新的时间复杂度可以近似为O(logn),这是很理想的;
大家可以自行查阅文章
#include
using namespace std;
const int N=10005;
const int INF=0x3f3f3f3f;
int n,a[N];
struct node
{
int l,r,mx;//左右端点以及最值 这里也可以换成区间值,根据需求就行
int lazy_tag;//懒惰标记;
}tree[N*4];//一定要开4倍n空间;
void f(int k,int lazy_tag)
{
tree[k].lazy_tag+=lazy_tag;
tree[k].mx+=lazy_tag;
}
void pushdown(int k)//懒惰标记下传
{
f(k*2,tree[k].lazy_tag);
f(k*2+1,tree[k].lazy_tag);
tree[k].lazy_tag=0;//懒惰标记置零
}
void build(int k,int l,int r)//递归建立线段树,k表示存储下标;
{
tree[k].l=l;
tree[k].r=r;
if(l==r)//递归出口
{
tree[k].mx=a[l];//最值就是a[l]存储的值
return;
}
int mid=l+r>>1;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
tree[k].mx=max(tree[k*2].mx,tree[k*2+1].mx);//当前区间最值即为左右孩子的最值;
}
void push(int k,int l,int r,int v)//区间更新,区间加减v
{
if(tree[k].l>=l&&tree[k].r<=r)//如果区间被覆盖
{
tree[k].mx+=v;
tree[k].lazy_tag=v;//打上懒惰标记
return;
}
pushdown(k);
int mid=(tree[k].l+tree[k].r)>>1;
if(l<=mid)
push(k*2,l,r,v);
if(r>mid)
push(k*2+1,l,r,v);
tree[k].mx=max(tree[k*2].mx,tree[k*2+1].mx);
}
void update(int k,int i,int v)//点更新,将a[i]修改更新为v,并维护线段树
{
if(tree[k].l==tree[k].r&&tree[k].l==i)//如果是叶子结点而且l=i;
{
tree[k].mx=v;
return;
}
int mid=(tree[k].l+tree[k].r)/2;
if(i<=mid)//在左子树
update(k*2,i,v);
else
update(k*2+1,i,v);
tree[k].mx=max(tree[k*2].mx,tree[k*2+1].mx);//回归时更新最值维护线段树;
}
int query1(int k,int l,int r)//用区间覆盖的方法 查询区间l-r的最值 (l,r)不改变;
{
if(tree[k].l>=l&&tree[k].r<=r)//区间覆盖
return tree[k].mx;
if(tree[k].lazy_tag)//先下传懒惰标记
pushdown(k);
int mid=(tree[k].l+tree[k].r)/2;
int Max=-INF;//负无穷
if(l<=mid)
Max=max(Max,query1(k*2,l,r));
if(r>mid)
Max=max(Max,query1(k*2+1,l,r));
return Max;
}
int query2(int k,int l,int r)//区间相等查询l-r的最值 (l,r)改变
{
if(tree[k].l==l&&tree[k].r==r)
return tree[k].mx;
if(tree[k].lazy_tag)//先下传懒惰标记
pushdown(k);
int mid=(tree[k].l+tree[k].r)/2;
if(r<=mid)//左子树查询
return query2(k*2,l,r);
else if(l>mid)
return query2(k*2+1,l,r);
else
return max(query2(k*2,l,mid),query2(k*2+1,mid+1,r));//左右子树分别查询缩小范围
}
void print(int k)
{
if(tree[k].mx)
{
cout<<k<<" "<<tree[k].l<<" "<<tree[k].r<<" "<<tree[k].mx<<endl;
print(k*2);
print(k*2+1);
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
build(1,1,n);
int l,r,v,i;
cin>>l>>r;
cout<<query1(1,l,r)<<endl;
cout<<query2(1,l,r)<<endl;
cin>>i>>v;
update(1,i,v);
cout<<query1(1,l,r)<<endl;
cout<<query2(1,l,r)<<endl;
push(1,l,r,v);
cout<<query1(1,l,r)<<endl;
cout<<query2(1,l,r)<<endl;
cin>>l>>r;
cout<<query1(1,l,r)<<endl;
cout<<query2(1,l,r)<<endl;
return 0;
}