线段树概念和使用技巧

线段树概念和使用技巧

1.概念

线段树概念可以查看博客线段树详解 (原理,实现与应用)_岩之痕-CSDN博客_线段树和书籍《算法竞赛进阶指南》-李煜东。

2.使用技巧

线段树可以方便的查找任一区间的统计值;这么说还有点含糊。

本质上是,我们需要:

  1. 每次根据一系列点[X1,X2,…,Xn]的值[f(X1),f(X2),…,f(Xn)]来得到某些值a,b,c,…,这些值就是我们需要统计的统计数据。
  2. 并且支持更新一系列点[x1,x2,…,xn]的值[f(X1),f(X2),…,f(Xn)]
  3. 统计数据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<aiag(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;
            }
        }
    }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值