学习笔记 --- 线段树
来源:互联网 发布:淘宝有哪些奇葩商品 编辑:程序博客网 时间:2024/05/16 05:18
开始切割数据结构,种树之路,学习了一下线段树,写一下心得与体会
一线段树摆上:
线段树的原理:将[1,n]分解成若干特定的子区间(数量不超过4*n),然后,将每个区间[L,R]都分解为少量特定的子区间,通过对这些少量子区间的修改或者统计,来实现快速对[L,R]的修改或者统计。
线段树中有如下定理:
定理:n>=3时,一个[1,n]的线段树可以将[1,n]的任意子区间[L,R]分解为不超过个子区间。
定理:n>=3时,一个[1,n]的线段树可以将[1,n]的任意子区间[L,R]分解为不超过个子区间。
一般存储采用数组直接存储:
当前点的位置为now,那么它的左子树位置为now*2,右子树位置为now*2+1(和二叉树存储同理)
那么是线段树的模板(此处以一个维护和的线段树演绎)
建树过程:
void updata(int now){ sum[now]=sum[now<<1]+sum[now<<1|1];}//求和过程 //此过程线段树核心过程,一般不同的线段树,最大的不同处就在此处void build(int left,int right,int now){ if (left==right) { sum[now]=tree[left]; return; }//每当达到叶节点就把值更新一下 int mid=(left+right)>>1; build(left,mid,now<<1);//左子树建树 build(mid+1,right,now<<1|1);//右子树建树 updata(now); //容易遗忘但是特别重要的一个语句,在更新完子数后根据子数修改根}//建树过程
点修改过程:
更新一个点的值
void point_change(int now,int left,int right,int data,int locate){ if (left==right) { sum[now]+=data; return; }//如果到达这个点的位置,更新 int mid=(left+right)>>1; if (locate<=mid)//如果要修改的点位于左子树,向左查询 point_change(now<<1,left,mid,data,locate); else//反之向右 point_change(now<<1|1,mid+1,right,data,locate); updata(now);//仍旧不能忘,更新完点就要更新根} //点修改过程
区间修改过程:
更新区间【left,right】的值
标记的含义:
本节点的统计信息已经根据标记更新过了,但是本节点的子节点仍需要进行更新。即,如果要给一个区间的所有值都加上1,那么,实际上并没有给这个区间的所有值都加上1,而是打个标记,记下来,这个节点所包含的区间需要加1.打上标记后,要根据标记更新本节点的统计信息,比如,如果本节点维护的是区间和,而本节点包含5个数,那么,打上+1的标记之后,要给本节点维护的和+5。这是向下延迟修改,但是向上显示的信息是修改以后的信息,所以查询的时候可以得到正确的结果。有的标记之间会相互影响,所以比较简单的做法是,每递归到一个区间,首先下推标记(若本节点有标记,就下推标记),然后再打上新的标记,这样仍然每个区间操作的复杂度是O(log2(n))。
标记有相对标记和绝对标记之分:
相对标记是将区间的所有数+a之类的操作,标记之间可以共存,跟打标记的顺序无关(跟顺序无关才是重点)。
所以,可以在区间修改的时候不下推标记,留到查询的时候再下推。绝对标记是将区间的所有数变成a之类的操作,打标记的顺序直接影响结果,所以这种标记在区间修改的时候必须下推旧标记,不然会出错。
注意,有多个标记的时候,标记下推的顺序也很重要,错误的下推顺序可能会导致错误。
void section_change(int LEFT,int RIGHT,int data,int left,int right,int now)//LEFT,RIGHT表示需要更新的区间,data为更新值,left,right为当前点的控制区间,now当前位置{ if (LEFT<=left && RIGHT>=right)//如果本区间完全在更新的区间范围,就更新 { sum[now]+=data*(right-left+1); delta[now]+=data;//惰性标记,子区间的值需要依此修改 return; } int mid=(left+right)>>1; if (LEFT<=mid)//判断左子树和修改区间有无交集,有交集则递归 section_change(LEFT,RIGHT,data,left,mid,now<<1); if (RIGHT>mid)//原理同上 section_change(LEFT,RIGHT,data,mid+1,right,now<<1|1); updata(now);//同理需要更新根}//区间修改过程
区间查询过程:
查询区间【left,right】的值
void pushdown(int now,int leftn,int rightn)//把根的标记下放到子数,确保答案的正确性{ if (delta[now]!=0) { delta[now<<1]+=delta[now]; delta[now<<1|1]+=delta[now];//标记下推 sum[now<<1]+=delta[now]*leftn; sum[now<<1|1]+=delta[now]*rightn;//修改子节点的值 delta[now]=0;//清除当前点的标记 } }//标记下放函数int query(int LEFT,int RIGHT,int left,int right,int now){ if (LEFT<=left && RIGHT>=right) return sum[now];//在区间内,直接返回即可 int mid=(left+right)>>1; pushdown(now,mid-left+1,right-mid);//标记下推(不加这步,结果出错) int total=0;//累计答案 if (LEFT<=mid) total+=query(LEFT,RIGHT,left,mid,now<<1); if (RIGHT>mid) total+=query(LEFT,RIGHT,mid+1,right,now<<1|1); return total; } //区间查询
主过程的调用:
int main(){//建树 Build(1,n,1); //点修改 Update(L,C,1,n,1); //区间修改 Update(L,R,C,1,n,1); //区间查询 int ANS=Query(L,R,1,n,1); }
说了这么多,还是需要大量题目的练习( ⊙ o ⊙ )啊!…..
- 学习笔记 线段树
- 线段树学习笔记
- 线段树学习笔记
- 学习笔记 --- 线段树
- 线段树学习笔记
- 线段树学习笔记
- 线段树学习笔记
- 线段树学习笔记01
- Algorithm学习笔记 --- 线段树
- C#学习笔记:线段树
- 线段树学习笔记//日记
- Algorithm学习笔记 --- 线段树单点更新
- 学习笔记:可持久化线段树
- |算法讨论|线段树2 学习笔记
- 线段树学习笔记及模板
- 【线段树】Codevs线段树练习1.2.3及线段树学习笔记
- 学习笔记——线段树算法学习
- unity 渲染线段 学习笔记
- 二叉树的层序遍历
- 单词接龙
- 1002.写出这个数
- 杭电ACM1134——Game of Connections
- iOS:openURL
- 学习笔记 --- 线段树
- Android 图片压缩
- Sublime Text3 编译多个文件
- 网站提权
- 使用Visio画用例图没有include(包含)关系的解决办法
- ssh机器间添加信任关系
- Android开发 在AndoridStudio中引入GreenDAO
- GreenDao常用操作(一)
- hdu2604 Queuing