学习笔记 --- 线段树
2015-12-04 19:24
597 查看
开始切割数据结构,种树之路,学习了一下线段树,写一下心得与体会
一线段树摆上:
线段树的原理:将[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 ⊙ )啊!…..
相关文章推荐
- C#数据结构之顺序表(SeqList)实例详解
- Lua教程(七):数据结构详解
- 解析从源码分析常见的基于Array的数据结构动态扩容机制的详解
- C#数据结构之队列(Quene)实例详解
- C#数据结构揭秘一
- C#数据结构之单链表(LinkList)实例详解
- 数据结构之Treap详解
- C#数据结构之堆栈(Stack)实例详解
- C#数据结构之双向链表(DbLinkList)实例详解
- JavaScript数据结构和算法之图和图算法
- Java数据结构及算法实例:冒泡排序 Bubble Sort
- Java数据结构及算法实例:插入排序 Insertion Sort
- Java数据结构及算法实例:考拉兹猜想 Collatz Conjecture
- java数据结构之java实现栈
- java数据结构之实现双向链表的示例
- Java数据结构及算法实例:选择排序 Selection Sort
- Java数据结构及算法实例:朴素字符匹配 Brute Force
- Java数据结构及算法实例:汉诺塔问题 Hanoi
- Java数据结构及算法实例:快速计算二进制数中1的个数(Fast Bit Counting)
- java数据结构和算法学习之汉诺塔示例