借着题解系统得梳理一下对于线段树的理解
先建立一个结构体为下面代码实现打基础
struct tree{ int l,r;//存储结点管理的区间范围 long long pre,add;//pre代表区间权值,add为懒标记,下文会进行讲解 }t[1000005];
首先,什么是线段树呢?线段树属于完全二叉树,其中每一个子节点而言,都表示整个序列中的一段子区间。由第一层结点储存单个元素,每个子节点不断向自己的父亲节点传递信息,而父节点存储的信息则是他的每一个子节点信息的整合。
用数组建立这种数据结构(建树)可以通过如下的递归函数来实现:
void bulid(int p,int l,int r) { t[p].l=l;t[p].r=r;//以p为编号的节点维护的区间为l到r if(l==r){//l=r的话,这个区间就只有一个数,直接让区间维护的值等于a[i] t[p].pre=a[l]; return; }//否则值等于左结点加右结点 int mid=l+r>>1; bulid(p*2,l,mid); bulid(p*2+1,mid+1,r); t[p].pre=t[p*2].pre+t[p*2+1].pre; }
那么,当数据结构已经建成,我们怎样对其中的数据进行修改和查询呢?
这里要借助一种工具:懒标记。懒标记的作用是记录每次、每个节点要更新的值。由于每个父节点下面都连着子节点,父节点和子节点需要同时实现值的更新。因此,懒标记从最初的父节点开始表示,在父节点值更新后,把子节点的懒标记更新为父节点懒标记,父节点本身懒标记为0。光说可能难以理解,请看代码:
void spread(int p){ if(t[p].add){//如果懒标记不为0,修改左右结点的值 t[p*2].pre+=t[p].add*(t[p*2].r-t[p*2].l+1); t[p*2+1].pre+=t[p].add*(t[p*2+1].r-t[p*2+1].l+1); t[p*2].add+=t[p].add;//该节点的左右结点打上标记 t[p*2+1].add+=t[p].add; t[p].add=0;//将该节点的懒标记清0 } }
现在我们就可以愉快地进行数据修改了,从根节点开始搜索,如果某个结点表示的区间完全包含于要修改的值的范围,那么对该结点的值进行上述的懒标记更新。
void change(int p,int x,int y,int z){ if(x<=t[p].l && y>=t[p].r)//被覆盖的话,就对其进行修改 { t[p].pre+=(long long)z*(t[p].r-t[p].l+1); t[p].add+=z;//打上懒标记 return; } spread(p);//如果发现没有被覆盖,那就需要继续向下找,将懒标记下放 int mid=t[p].l+t[p].r>>1; if(x<=mid) change(p*2,x,y,z);//如果要修改的区间覆盖了左结点,就修改左结点 if(y>mid) change(p*2+1,x,y,z);//右结点同理 t[p].pre=t[p*2].pre+t[p*2+1].pre;//最终的值等于左结点的值+右结点的值 }
到此为止,我们只剩下值的查询一个问题,但不用想都能发现,查询和修改根本没有什么区别,通过懒标记同理实现即可。
long long ask(int p,int x,int y) { if(x<=t[p].l && y>=t[p].r) return t[p].pre;//如果被覆盖,就返回值 spread(p);//使用懒标记,查询左右结点 int mid=t[p].l+t[p].r>>1; long long ans=0; if(x<=mid) ans+=ask(p*2,x,y); if(y>mid) ans+=ask(p*2+1,x,y);//累加答案,返回左右结点值的和 return ans; }
穿在一起,就构成了线段树的基本操作了