线段树学习小结

线段树,嗯,是个好东西,可以高效率解决一些区间问题,一般来讲,对于这些问题,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;

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


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值