线段树概念和使用技巧
1.概念
线段树概念可以查看博客线段树详解 (原理,实现与应用)_岩之痕-CSDN博客_线段树和书籍《算法竞赛进阶指南》-李煜东。
2.使用技巧
线段树可以方便的查找任一区间的统计值;这么说还有点含糊。
本质上是,我们需要:
- 每次根据一系列点[X1,X2,…,Xn]的值[f(X1),f(X2),…,f(Xn)]来得到某些值a,b,c,…,这些值就是我们需要统计的统计数据。
- 并且支持更新一系列点[x1,x2,…,xn]的值[f(X1),f(X2),…,f(Xn)]。
- 统计数据a,b,c…满足区间可加性。
当然上面说的一系列点也可以是一个。
其实只想实现上述目的,直接用传统的数组也可以,但是每次查找或者更新一个集合[X1,X2,…,Xn]需要O(N)的时间复杂度,线段树在查找和更新时,只需要O(logN)的时间复杂度。
线段树之所以快是利用了区间可加性:
如果每次想知道点[x1,x2,x3,x4,x5]的最大值,线段树的思想是,如果我们保存了[x1,x2,x3]的最大值为a,并且保存了[x4,x5]的最大值为b;则可以知道a,b中的最大值就是计算结果。区间可加性,就是大区间的值可以通过分成的小区间的值来得到。
线段树为了好实现,使用了区间[a,b]代表所有大于等于a,小于等于b的点集合。
3.线段树案例
1.leetcode 699
每次我们放一个箱子的时候(假如其高度为h),都需要知道该箱子底边能覆盖住的点中的最大高度max(每次根据一系列点[X1,X2,…,Xn]的值[f(X1),f(X2),…,f(Xn)]来得到某些值a,b,c,…;这里是只需要得到一个值max),则该箱子放下后所有被其覆盖住的点的高度就要变成max+h(支持更新一系列点[x1,x2,…,xn]的值f(x));则每次放完箱子后,所达到的高度就是所有点的最大高度(又需要查找所有点的值f(x)来得到max);并且求一系列点的max满足区间可加性;所以满足线段树的使用条件,我们可以使用线段树从而支持每次O(logN)级别的查询和更新一系列点。
2.leetcode 673
这道题可以使用动态规划,f(a)表示以a结尾的最长递增子序列的长度,g(a)表示以a结尾的最长递增子序列的数量。
则状态转移为:
f
(
a
)
=
m
a
x
(
f
(
i
)
)
如
果
i
<
a
并
且
i
在
原
数
组
中
排
在
a
前
面
g
(
a
)
+
=
g
(
i
)
如
果
f
(
i
)
=
=
f
(
a
)
f(a) = max(f(i)) \ 如果i<a并且i在原数组中排在a前面\\ g(a) += g(i) \ 如果f(i)==f(a)
f(a)=max(f(i)) 如果i<a并且i在原数组中排在a前面g(a)+=g(i) 如果f(i)==f(a)
显然我们每次计算f(a)和g(a)时,都要查找一系列点的f(i)和g(i)来得到结果,并且使用结果来更新(赋值)f(a)和g(a)。如果使用dp数组则,每次查找需要O(n)的时间复杂度,更新因为只更新当前计算值f(a)和g(a)则每次时间复杂度为O(1),所以使用dp数组的总时间复杂度为O(n^2)。
很明显,上面的查找和更新都符合线段树的使用场景,并且可以证明我们求值f(a)和g(a)的过程,也满足区间可加性:
假如现在知道以[a,b,c,d]序列中的每个值结尾能达到的最大递增子序列长度l和数量c。现在知道[a,b]中的值结尾能达到的长度和数量为l1,c1;[c,d]中的值结尾能达到的长度和数量为l2,c2。则:
if(l1==l2) c=c1+c2;
else if(l1>l2) c=c1;
else c=c2
所以,大区间的值可以通过小区间的值得到,满足区间可加性。
因此可以使用线段树,则每次查找和更新的时间复杂度为O(logn),总的时间复杂度仅为O(nlogn)
区间可加性。
因此可以使用线段树,则每次查找和更新的时间复杂度为O(logn),总的时间复杂度仅为O(nlogn)
4.线段树代码
递归实现样例
- 线段树有时候不需要写pushUp(当我们不需要区间查询时)
- 线段树有时候不需要写pushdown(当我们不需要区间修改时)
- 但是不管什么情况,都写不会影响正确性,但会影响效率。
class SegmentTree{
Node root;
SegmentTree(int l,int r){
root = bulid(l,r);
}
public int getMid(int l,int r){
return l+((r-l) >> 1);
}
//建树
public Node bulid(int l,int r){
Node node = new Node(l,r,0,0);
if(l==r) return node;
int mid = getMid(l,r);
node.left = bulid(l,mid);
node.right = bulid(mid+1,r);
return node;
}
//pushUp 更新区间信息
public void pushUp(Node node){
node.max = node.left.max>node.right.max?node.left.max:node.right.max;
}
//pushDown 下推标记记录
public void pushDown(Node node){
if(node.sign>0){
node.left.max = node.max;
node.right.max = node.max;
node.left.sign = node.sign;
node.right.sign = node.sign;
node.sign = 0;
}
}
//更新区间
public void update(Node node,int l,int r,int max){
if(node.start>r || node.end < l) return;
if(node.start>=l && node.end<=r){
node.max = max;
node.sign = max;
return;
}
pushDown(node)
update(node.left,l,r,max);
update(node.right,l,r,max);
pushUp(node);
}
//查找区间
public int query(Node node,int l,int r){
if(node.start>r || node.end<l || l>r) return 0;
if(node.start>=l && node.end<=r) return node.max;
pushDown(node);
int left = query(node.left,l,r);
int right = query(node.right,l,r);
return left>right?left:right;
}
class Node{
int start,end;
int max;
int sign;
Node left;
Node right;
public Node(int start, int end, int max, int sign) {
this.start = start;
this.end = end;
this.max = max;
this.sign = sign;
}
}
}