1、线段树简介
线段树是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。
如果线段树中的一个非叶子节点编号为x,他的左儿子的编号为(x<<1),他的右儿子编号为(x<<1|1)。
2、Some Questions
a)为什么线段树要开4倍空间? 线段树并不一定是完全二叉树,可能会出现以下这种情况:
有些编号比较小的节点变成了叶节点,但比他大的编号里出现了非叶结点,所以会用到更大的编号。
b)线段树的时间复杂度:用线段树对“编号连续”的一些点,进行修改或者统计操作,修改和统计的复杂度都是O(log n)
c)如何设置lazytag?如何用设置的lazy_tag更新所需数据值? Lazytag的设置,主要是要求:求sum的时候,根据从父区间传下来的lazy_tag,能正确更新子区间所有内部数据值和他的lazy值,从而正确求出sum
3、线段树的组成
struct node
{
int l,r,w,f; ///l,r代表区间,w代表区间和,f为懒标记
}tree[N*4+10];
build 函数
void build(int l,int r,int x) ///x为当前节点编号
{
tree[x].l=l;tree[x].r=r;
if(l==r)
{
scanf("%d",&tree[x].w);
return ;
}
build(l,(l+r)/2,x*2);
build((l+r)/2+1,r,x*2+1);
tree[x].w=tree[x*2].w+tree[x*2+1].w;
}
区间查询
void sum(int k)
{
if(tree[k].l>=x&&tree[k].r<=y)///查询[x,y]区间和
{
ans+=tree[k].w;
return;
}
int m=(tree[k].l+tree[k].r)/2;
if(x<=m) sum(k*2);
if(y>m) sum(k*2+1);
}
区间修改
修改的时候只修改对查询有用的点,这是区间修改的关键思路。为了实现这个,我们引入一个新的状态——懒标记。
递归到这个节点时,只更新这个节点的状态,并把当前的更改值累积到标记中。
当需要递归这个节点的子节点时,标记下传给子节点。
懒标记的下传
①当前节点的懒标记累积到子节点的懒标记中。
②修改子节点状态。原状态+子节点区间点的个数*父节点传下来的懒标记。
③父节点懒标记清0。
a)懒标记下传
void down(int k)
{
tree[k*2].f+=tree[k].f;
tree[k*2+1].f+=tree[k].f;
tree[k*2].w+=tree[k].f*(tree[k*2].r-tree[k*2].l+1);
tree[k*2+1].w+=tree[k].f*(tree[k*2+1].r-tree[k*2+1].l+1);
tree[k].f=0;
}
b)区间查询
void add(int k)
{
if(tree[k].l>=a&&tree[k].r<=b)//当前区间全部对要修改的区间有用
{
tree[k].w+=(tree[k].r-tree[k].l+1)*x;//(r-1)+1区间点的总数
tree[k].f+=x;
return;
}
if(tree[k].f) down(k);//懒标记下传。只有不满足上面的if条件才执行,所以一定会用到当前节点的子节点
int m=(tree[k].l+tree[k].r)/2;
if(a<=m) add(k*2);
if(b>m) add(k*2+1);
tree[k].w=tree[k*2].w+tree[k*2+1].w;//更改区间状态
}
完整的代码:
#include<cstdio>
using namespace std;
int n,p,a,b,m,x,y,ans;
struct node
{
int l,r,w,f;
}tree[400001];
inline void build(int k,int ll,int rr)//建树
{
tree[k].l=ll,tree[k].r=rr;
if(tree[k].l==tree[k].r)
{
scanf("%d",&tree[k].w);
return;
}
int m=(ll+rr)/2;
build(k*2,ll,m);
build(k*2+1,m+1,rr);
tree[k].w=tree[k*2].w+tree[k*2+1].w;
}
inline void down(int k)//标记下传
{
tree[k*2].f+=tree[k].f;
tree[k*2+1].f+=tree[k].f;
tree[k*2].w+=tree[k].f*(tree[k*2].r-tree[k*2].l+1);
tree[k*2+1].w+=tree[k].f*(tree[k*2+1].r-tree[k*2+1].l+1);
tree[k].f=0;
}
inline void ask_point(int k)//单点查询
{
if(tree[k].l==tree[k].r)
{
ans=tree[k].w;
return ;
}
if(tree[k].f) down(k);
int m=(tree[k].l+tree[k].r)/2;
if(x<=m) ask_point(k*2);
else ask_point(k*2+1);
}
inline void change_point(int k)//单点修改
{
if(tree[k].l==tree[k].r)
{
tree[k].w+=y;
return;
}
if(tree[k].f) down(k);
int m=(tree[k].l+tree[k].r)/2;
if(x<=m) change_point(k*2);
else change_point(k*2+1);
tree[k].w=tree[k*2].w+tree[k*2+1].w;
}
inline void ask_interval(int k)//区间查询
{
if(tree[k].l>=a&&tree[k].r<=b)
{
ans+=tree[k].w;
return;
}
if(tree[k].f) down(k);
int m=(tree[k].l+tree[k].r)/2;
if(a<=m) ask_interval(k*2);
if(b>m) ask_interval(k*2+1);
}
inline void change_interval(int k)//区间修改
{
if(tree[k].l>=a&&tree[k].r<=b)
{
tree[k].w+=(tree[k].r-tree[k].l+1)*y;
tree[k].f+=y;
return;
}
if(tree[k].f) down(k);
int m=(tree[k].l+tree[k].r)/2;
if(a<=m) change_interval(k*2);
if(b>m) change_interval(k*2+1);
tree[k].w=tree[k*2].w+tree[k*2+1].w;
}
int main()
{
scanf("%d",&n);//n个节点
build(1,1,n);//建树
scanf("%d",&m);//m种操作
for(int i=1;i<=m;i++)
{
scanf("%d",&p);
ans=0;
if(p==1)
{
scanf("%d",&x);
ask_point(1);//单点查询,输出第x个数
printf("%d",ans);
}
else if(p==2)
{
scanf("%d%d",&x,&y);
change_point(1);//单点修改
}
else if(p==3)
{
scanf("%d%d",&a,&b);//区间查询
ask_interval(1);
printf("%d\n",ans);
}
else
{
scanf("%d%d%d",&a,&b,&y);//区间修改
change_interval(1);
}
}
}
4、一些例题
a)HDU1166 非常经典的题,可以用树状数组做,当然线段树入门的小白(like me 也可以用来练手。
主要用到单点修改和区间查询,所以不需要懒标记,主要代码非常模版,如下:
void build(int l,int r,int k) ///建树
{
tree[k].l=l;
tree[k].r=r;
if(l==r)
{
scanf("%d",&tree[k].w);
return ;
}
int m=(l+r)/2;
build(l,m,k*2);
build(m+1,r,k*2+1);
tree[k].w=tree[k*2].w+tree[k*2+1].w;
}
void add(int k) ///单点修改
{
if(tree[k].l==tree[k].r)
{
tree[k].w+=y;
return;
}
int m=(tree[k].l+tree[k].r)/2;
if(x<=m) add(k*2);
else add(k*2+1);
tree[k].w=tree[k*2].w+tree[k*2+1].w;
}
inline void ask_interval(int k) ///区间查询
{
if(tree[k].l>=a&&tree[k].r<=b)
{
ans+=tree[k].w;
return;
}
int m=(tree[k].l+tree[k].r)/2;
if(a<=m) ask_interval(k*2);
if(b>m) ask_interval(k*2+1);
}
b)HDU 1754 维护区间最大值+单点修改 AC代码:
void build(int l,int r,int k)
{
tree[k].l=l;
tree[k].r=r;
if(l==r)
{
scanf("%d",&tree[k].w);
return ;
}
int m=(l+r)/2;
build(l,m,k*2);
build(m+1,r,k*2+1);
tree[k].w=max(tree[k*2].w,tree[k*2+1].w);
}
void add(int k)
{
if(tree[k].l==tree[k].r)
{
tree[k].w=y;
return;
}
int m=(tree[k].l+tree[k].r)/2;
if(x<=m) add(k*2);
else add(k*2+1);
tree[k].w=max(tree[k*2].w,tree[k*2+1].w);
}
inline void ask_interval(int k)
{
if(tree[k].l>=a&&tree[k].r<=b)
{
ans=max(ans,tree[k].w);
return;
}
int m=(tree[k].l+tree[k].r)/2;
if(a<=m) ask_interval(k*2);
if(b>m) ask_interval(k*2+1);
}