线段树学习小结

来源:互联网 发布:路老膏方 常见网络骗 编辑:程序博客网 时间:2024/06/05 00:18

线段树,嗯,是个好东西,可以高效率解决一些区间问题,一般来讲,对于这些问题,RMQ的效率应该是没有线段树高吧?(我不会RMQ,说错别打我(o´・ェ・`o))


线段树是一棵完全二叉树,主要用于记录区间,执行区间加减,求和,查询最大最小等一系列操作,时间复杂度一般实在log级别的。(以下用最大值为例)
每个区间为left~right。
———————–主要操作————————–
一:建(造线段)树:通过二分区间,当left=right时,便可以赋值了。回溯时,每个非叶子节点记录要求值的区间(最大值,最小值,和什么的。)
图就不给了,地址好长好麻烦,看不顺眼。(原谅我我有强迫症)

procedure        maketree(p,l,r:longint);var        mid:longint;begin        if l=r then tree[p]:=a[l]        else        begin                mid:=(l+r) div 2;                maketree(p*2,l,mid);                maketree(p*2+1,mid+1,r);                if tree[p*2]>tree[p*2+1] then tree[p]:=tree[p*2]                else tree[p]:=tree[p*2+1];        end;end;

二:区间查询:假设要查询一个区间[1~8]的数组里区间[1~4]里的最大值,我们知道线段树里除了叶子节点,其他节点都是记录该区间的最大值的那个编号,所以我们二分查找就可以了,当搜到的区间的范围对应,便可以比较,更新答案了。
问:如果我要搜的是[1~6]的最大值会如何?
我们发现了因为线段树的是二叉树,所以会把[1~8]分成[1~4]或[5~8]两个区间,我们要搜[1~4]或[5~8]很简单,那搜[1~6]该怎么办?
也很简单,分开即可,分别搜索[1~4]或[5~6]就可以了。

procedure        find(p,l,r,a,b:longint);var        mid:longint;begin        if (l=a)and(r=b) then        begin                if ans<tree[p] then ans:=tree[p];        end        else        begin                mid:=(l+r) div 2;                if b<=mid then find(p*2,l,mid,a,b)                else if mid<a then find(p*2+1,mid+1,r,a,b)                else                begin                        find(p*2,l,mid,a,mid);                        find(p*2+1,mid+1,r,mid+1,b);                end;        end;end;

三:修改区间内某一个值(单节点修改)
比较简单,二分区间到要的地方,修改区间值即可。

procedure        check(p,l,r:longint);var        mid:longint;begin        if l=r then tree[p]:=y//y是要求改成的值。        else begin                mid:=(l+r) div 2;                if x<=mid then check(p*2,l,mid)                else check(p*2+1,mid+1,r);                if tree[p*2]>tree[p*2+1] then tree[p]:=tree[p*2]                else tree[p]:=tree[p*2+1];        end;end;

四:修改一段区间的值(区间修改)
重点!也是线段树内涉及到最精华的部分——延迟标记(简称“Lazy标记,懒标记”)。
其实也没什么,就是我们在往下修改时,因为是区间修改,不可能在一次修改时就把全部一个个修改掉,这样和暴力有区别?
所以我们在我们要修改的区间上打下一个标记,告诉我们这个区间要修改但我们还没修改,下一次查询啊,修改的时候,顺便将这有标记的区间修改就是了。

procedure insert(x,l,r,st,en,value:longint);var        m:longint;begin        if (l=st)and(r=en) then        begin                inc(tree[x].maxvalue,value);//maxvalue表示为该区间的最大值的位置。                inc(tree[x].add,value);//add表示该区间的lazy标记。        end        else        begin                inc(tree[2*x].maxvalue,tree[x].add);//当我们遇到这个有懒标记的区间,顺便修改。也就是将标记下传。                inc(tree[2*x].add,tree[x].add);                inc(tree[2*x+1].maxvalue,tree[x].add);                inc(tree[2*x+1].add,tree[x].add);                tree[x].add:=0;//注意清空标记,表明这里的区间我们已经修改了。                m:=(l+r)shr 1;                if en<=m then insert(2*x,l,m,st,en,value)                else if st>m then insert(2*x+1,m+1,r,st,en,value)                else                begin                        insert(2*x,l,m,st,m,value);                        insert(2*x+1,m+1,r,m+1,en,value);                end;                tree[x].maxvalue:=max(tree[2*x].maxvalue,tree[2*x+1].maxvalue);        end;end;

当然,涉及到乘除加减的修改时,注意懒标记是应该用怎样的顺序。


其实线段树挺优秀,但是被人们说成空间复杂度较大,并且时间效率不是在理想中的好,编程复杂度高。
其实可以发现每个部分其实是很相像的,稍微留意些细节便是可以了。
这里写图片描述

0 0
原创粉丝点击