线段树解析
来源:互联网 发布:变声软件怎么实现 编辑:程序博客网 时间:2024/06/05 16:31
1.为什么要用线段树
例1:有M个数排成一列,初始值全为0,然后做N次操作,每次我们可以进行如下操作:
(1)将指定区间的每个数加上一个值;
(2)将指定区间的所有数置成一个值;
(3)询问一个区间上的最小值、最大值、所有数的和。
一般的模拟算法:
用一张线性表表示整个数列,每次执行前两个操作的时候,将对应区间里的数值逐一进行修改,
执行第三个操作的时候,线性扫描询问区间,求出三个统计值,每次维护的时间复杂度O(m),整体的时间复杂度O(mn)。
readln(m,n);for i:=1 to n dobegin read(t); if t=1 //指定区间加上一个值 then begin readln(left,right,x); for j:=left to right do a[j]:=a[j]+x; end;if t=2 //指定区间置成一个值then begin readln(left,right,x); for j:=left to right do a[j]:=x; end;if t=3 //询问一个区间的最小值、最大值、和then begin readln(left,right); min:=a[left]; max:=a[left]; sum:=a[left]; for j:=left+1 to right do begin if a[j]<min then min:=a[j]; if a[j]>max then max:=a[j]; sum:=sum+a[j]; end; writeln(min,' ',max,' ',sum); end;
M,N的规模
一般的模拟
M=100000,N=5000
2.06秒
M=100000,N=10000
4.14秒
M=100000,N=50000
21.32秒
M=100000,N=100000
43.36秒
当m、n的值比较大时,这个算法就太低效了。
其低效的原因主要是我们在每一次操作中都是针对每个元素进行维护的,而这里我们进行的操作都是针对一个区间进行操作的。
假如设计一种数据结构,能够直接维护所需处理的区间,那么就能更加有效地解决这个问题。
定义1:长度为1的线段称为元线段。
定义2:一棵树被称为线段树,当且仅当这棵树满足如下条件:
(1)该树是一棵二叉树;
(2)树中的每一个结点都对应一条线段[a,b];
(3)树中的结点是叶子结点,当且仅当它所代表的线段是元线段;
(4)树中非叶子结点都有左右两棵子树,左子树树根对应线段[a,(a+b)/2],右子树树根对应线段[(a+b)/2,b]。
常用的是需要对数列进行处理,将区间[a,b]分解为[a,(a+b)/2],[(a+b)/2+1,b],当a=b时表示为一个叶子结点,表示数列中的一个数。
3.线段树的性质
性质1:长度范围为[1,L]的一棵线段树的深度不超过log2(L-1)+1;
性质2:线段树上的结点个数不超过2L个;
性质3:线段树把区间上的任意一条线段都分成不超过2log2L条线段。
4.线段树的存储方式
(1)链表实现:
Node=^TNode;
TNode=record
Left,Right:integer;
LeftChild,RightChild:Node;
end;
(2)数组模拟链表:
Left,Right,LeftChild,RightChild:array[1..n] of integer;
(3)堆的结构:
根节点为1,对于结点x,其左孩子为2x,右孩子为2x+1
5.线段树的构造
procedure build(cur:Node;l,r:integer);begin cur^.Left:=l; cur^.Right:=r; if l=r then begin cur^.LeftChild:=nil; cur^.RightChild:=nil; end else begin new(cur^.LeftChild); new(cur^.RightChild); build(cur^.LeftChild,l,(l+r) div 2); build(cur^.RightChild,(l+r) div 2+1,r); end; end;6.线段树的查询
线段树的查询procedure query(cur:Node;l,r:integer);var LC,RC:Node;begin LC:=cur^.LeftChild; RC:=cur^.RightChild; if (l<=cur^.Left) and (cur^.Right<=r) then writeln(cur^.Left,‘ ’,cur^.Right); //可以增加其他操作 else begin if l<=(cur^.Left+cur^.Right) div 2 then query(LC,l,r); if r>(cur^.Left+cur^.Right) div 2 then query(RC,l,r); end;end;
7.线段树的维护
如果想要让线段树发挥实际的作用,必须在每个结点上维护额外的域来记录更多的信息。
首先看一个引例的弱化版,假设每次修改操作只能对其中某个元素进行修改。
在每个结点上额外维护3个域:max、min、sum,分别表示子树上的最大值、最小值和所有元素的和。
procedure insert(cur:Node;x,num:integer);var LC,RC:Node;begin LC:=cur^.LeftChild; RC:=cur^.RightChild; if cur^.Left=cur^.Right //对叶结点的处理 then begin cur^.Min:=num; cur^.Max:=num; cur^.Sum:=num; end else begin if x<=(cur^.Left+cur^.Right) div 2 then insert(LC,x,num); if x>(cur^.Left+cur^.Right) div 2 then insert(RC,x,num); cur^.Sum:=LC^.Sum+RC^.Sum; //上推 if (LC^.Max>RC^.Max) then cur^.Max:=LC^.Max else cur^.Max:=RC^.Max; if (LC^.Min<RC^.Min) then cur^.Min:=LC^.Min else cur^.Min:=RC^.Min; end;end;9.对区间进行查询
function querysum(cur:Node;l,r:integer):integer;var LC,RC:Node; ret:integer;begin LC:=cur^.LeftChild; RC:=cur^.RightChild; ret:=0; if (l<=cur^.Left) and (cur^.Right<=r) then ret:=cur^.Sum else begin if l<=(cur^.Left+cur^.Right) div 2 then ret:=ret+querysum(LC,l,r); if r>(cur^.Left+cur^.Right) div 2 then ret:=ret+querysum(RC,l,r); end; querysum:=ret;end;
10.对区间修改(统一增加一个数)
procedure update(cur:Node;l,r,delta:integer);var LC,RC:Node; //需要给每个区间增加一个变量deltabegin LC:=cur^.LeftChild; RC:=cur^.RightChild; if (l<=cur^.Left) and (cur^.Right<=r) then cur^.Delta:=cur^.Delta+delta //改变该区间的delta,注意sum else begin if l<=(cur^.Left+cur^.Right) div 2 then update(LC,l,r,delta); if r>(cur^.Left+cur^.Right) div 2 then update(RC,l,r,delta); cur^.Sum:=LC^.Sum+LC^.Delta*(LC^.Right-LC^.Left+1); cur^.Sum:=cur^.Sum+RC^.Sum+ //上推 RC^.Delta*(RC^.Right-RC^.Left+1); end;end;
11.对区间的查询
function querysum(cur:Node;l,r:integer):integer;var LC,RC:Node; ret:integer;begin LC:=cur^.LeftChild; RC:=cur^.RightChild; ret:=0; if (l<=cur^.Left) and (cur^.Right<=r) then ret:=ret+cur^.Sum+(cur^.Right-cur^.Left+1)*cur^.Delta else begin //将该区间的delta下传 给左右区间,同时修改sum的值 cur^.sum:=cur^.sum+(cur^.right-cur^.left+1)*cur^.delta; LC^.Delta:=LC^.Delta+cur^.Delta; RC^.Delta:=RC^.Delta+cur^.Delta; cur^.Delta:=0; if l<=(cur^.Left+cur^.Right) div 2 then ret:=ret+querysum(LC,l,r); if r>(cur^.Left+cur^.Right) div 2 then ret:=ret+querysum(RC,l,r); end; querysum:=ret;end;
12.对区间的修改(设置为同一个数)
procedure update(cur:Node;l,r,num:integer);var LC,RC:Node;begin LC:=cur^.LeftChild; RC:=cur^.RightChild; if cur^.En //如果该区间已经是同一个值,将值下传给左右区间 then begin cur^.sum:=(cur^.right-cur^.left+1)*cur^.data; if LC<>nil then begin LC^.Data:=cur^.Data; LC^.En:=true; end; if RC<>nil then begin RC^.Data:=cur^.Data; RC^.En:=true; end; cur^.En:=false; end; if (l<=cur^.Left) and (cur^.Right<=r) then begin cur^.En:=true; cur^.Data:=num; end else begin if l<=(cur^.Left+cur^.Right) div 2 then update(LC,l,r,num); if r>(cur^.Left+cur^.Right) div 2 then update(RC,l,r,num); cur^.Sum:=calcsum(LC)+calcsum(RC); end;end;
13.对区间查询
function querysum(cur:Node;l,r:integer):integer;var LC,RC:Node; ret:integer;begin LC:=cur^.LeftChild; RC:=cur^.RightChild; ret:=0; if (l<=cur^.Left) and (cur^.Right<=r) then ret:=ret+calcsum(cur) else begin if cur^.En //如果该区间是同一个数,将值下传给左右区间 then begin LC^.Data:=cur^.Data; LC^.En:=true; RC^.Data:=cur^.Data; RC^.En:=true; cur^.En:=false; end; if l<=(cur^.Left+cur^.Right) div 2 then ret:=ret+querysum(LC,l,r); if r>(cur^.Left+cur^.Right) div 2 then ret:=ret+querysum(RC,l,r); end; querysum:=ret;end;
14.线段树的维护方法
线段树上的参数通常有两种维护方法:
(1)一类参数表达了结点的性质,通常具有树型的递推性质,可以从下向上进行递推计算;(如sum,max,min)
(2)一类参数表达了子树的性质,维护的时候可以先打上标记,在需要进一步访问其子结点的时候从上向下传递。(如delta,en)
线段树上维护或者询问过程的一般过程:
对于当前区间[l,r]
if 达到某种边界条件(如叶结点或被整个区间完全包含)then
对维护或是询问作相应处理
else
将第二类标记传递下去(注意,在查询过程中也要处理)
根据区间的关系,对两个孩子结点递归处理
利用递推关系,根据孩子结点的情况维护第一类信息
M,N的规模
一般的模拟
线段树
M=100000,N=5000
2.06秒
0. 57秒
M=100000,N=10000
4.14秒
0. 61秒
M=100000,N=50000
21.32秒
1.21秒
M=100000,N=100000
43.36秒
2.03秒
0 0
- 线段树解析
- 线段树解析
- 线段树解析
- 线段树和RMQ解析和模板
- 二维线段树解析 (HDU1823)解题报告
- 线段树解析以及代码模板
- hdu3577(线段树+lazy+样例解释+代码解析)
- 线段树?线段树!
- 线段树?线段树!
- 线段_线段树
- 线段_线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 2015互联网大会全球八站启动 ,首站4月28日北京亮相~
- Remove Duplicates from Sorted Array
- join方法
- mysql查询表中的列名
- 网络互联参考模型(详解)
- 线段树解析
- 学习笔记(二)——STL
- Trie树学习记录
- Android重复layout解决方法总结
- 【机器学习】相关博客收集
- wamp常见问题【0xc0007b】【重装phpmyadmin】
- 简述RFID系统的时隙ALOHA算法的工作过程。
- LeetCode - 3Sum
- 博弈问题及SG函数(真的很经典)