六月集训(24)线段树

这篇博客详细介绍了如何使用线段树解决LeetCode中的两道难题:731.我的日程安排表II和699.掉落的方块。对于731题,通过线段树实现区间查询和修改,避免三重预订;699题中,利用线段树记录方块叠加后的最高高度。文章深入解析了线段树的插入和查询操作,并解释了为何边界处理和区间累加的特殊之处。
摘要由CSDN通过智能技术生成

1.LeetCode:731. 我的日程安排表 II

原题链接


         实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。

        MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。

        当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订。

        每次调用 MyCalendar.book方法时,如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。

        请按照以下步骤调用MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)

        示例:

        MyCalendar();

        MyCalendar.book(10, 20); // returns true

        MyCalendar.book(50, 60); // returns true

        MyCalendar.book(10, 40); // returns true

        MyCalendar.book(5, 15); // returns false

        MyCalendar.book(5, 10); // returns true

        MyCalendar.book(25, 55); // returns true

        提示:

        每个测试用例,调用 MyCalendar.book 函数最多不超过 1000次。

        调用函数 MyCalendar.book(start, end)时, start 和 end 的取值范围为 [0, 10^9]。


        区间查询和修改利用线段树是一种基本的方法,按照本题要求我们要先检查每个操作的区间是否被完全覆盖了3次以上,如果有就直接返回false,否则进行插入。这里线段树的数据域有两个,懒惰标记lazy和被覆盖的情况mask,lazy记载改区间被完全覆盖的次数,mask的第i位二进制位存储该区间被覆盖了i次。

        这里由于数据较大我们要进行懒惰标记,首先看插入操作。这里首先要继承父带过来的懒惰标记和覆盖情况,然后看自己的区间是否被完全覆盖,如果完全无交集直接返回,如果被完全覆盖,lazy++,mask<<1代表该区间被覆盖次数+1,否则二分搜索能够被完全覆盖的区间。最后更新父的状态。

        查询操作和插入操作类似,首先要继承父带来的懒惰标记和覆盖状况,然后是查看区间的被覆盖情况,没有交集直接返回,被完全覆盖或上当前区间的mask直接返回(这里有可能要组合左右子树的覆盖状况,所以要用按位或),否则二分搜索合适区间,并更新父的状态。

        这里对可能有疑问的地方进行解释:

        (1)首先是(ans>>2)>=1的意义,ans是我们在整个区间内寻找到的目的区间的mask,那么ans就跟我们对于mask的定义一样,其二进制的第i位为1代表着被覆盖了i次(这里组合了左右子区间的覆盖状况),那么将其右移两位就代表着第2次之后的状况,如果大于等于了1就说明该区间被完全覆盖的次数大于等于了3,当然要返回false。


        (2)然后就是查找的区间为什么是[start,end-1]这里就是没有仔细读题可能带来的误解,因为题目中说过给出的区间是一个开区间也就是[start,end),自然我们搜索的右端点要-1。
完整代码如下:

class MyCalendarTwo {
    struct TreeNode{
        TreeNode* left,*right;
        int mask;
        int lazy;
        TreeNode(){
            left=right=nullptr;
            lazy=0;
            mask=1;
        }
    };
    void insert(TreeNode*& root,int l,int r,int start,int end,int lazy){
        if(l>end||r<start){
            return;
        }
        if(!root){
            root=new TreeNode();
        }
        root->lazy+=lazy;
        root->mask<<=lazy;
        if(start<=l&&r<=end){
            root->lazy++;
            root->mask<<=1;
            return;
        }
        int curl=root->lazy;
        root->lazy=0;
        int mid=l+((r-l)>>1);
        insert(root->left,l,mid,start,end,curl);
        insert(root->right,mid+1,r,start,end,curl);
        root->mask=0;
        if(root->left){
            root->mask|=root->left->mask;
        }
        if(root->right){
            root->mask|=root->right->mask;
        }

    }

    void query(TreeNode*& root,int l,int r,int start,int end,int lazy,int &ans){
        if(!root){
            if(!lazy){
                return;
            }
            root=new TreeNode();
        }
        root->lazy+=lazy;
        root->mask<<=lazy;
        if(l>end||r<start){
            return ;
        }
        if(start<=l&&r<=end){
            ans|=root->mask;
            return;
        }
        int curl=root->lazy;
        root->lazy=0;
        int mid=l+((r-l)>>1);
        query(root->left,l,mid,start,end,curl,ans);
        query(root->right,mid+1,r,start,end,curl,ans);
        root->mask=0;
        if(root->left ) root->mask|=root->left->mask;
        if(root->right) root->mask|=root->right->mask;
        return;

    }
    TreeNode* root;
public:
    MyCalendarTwo() {
        root=nullptr;
    }
    
    bool book(int start, int end) {
        int ans=0;
        query(root,0,1000000000,start,end-1,0,ans);
        if((ans>>2)>=1){
            return false;
        }
        insert(root,0,1000000000,start,end-1,0);
        return true;
    }
};

2.LeetCode:699. 掉落的方块

原题链接


        在二维平面上的 x 轴上,放置着一些方块。

        给你一个二维整数数组 positions ,其中 positions[i] = [lefti, sideLengthi] 表示:第 i 个方块边长为 sideLengthi ,其左侧边与 x 轴上坐标点 lefti 对齐。

        每个方块都从一个比目前所有的落地方块更高的高度掉落而下。方块沿 y 轴负方向下落,直到着陆到 另一个正方形的顶边 或者是 x 轴上 。一个方块仅仅是擦过另一个方块的左侧边或右侧边不算着陆。一旦着陆,它就会固定在原地,无法移动。

        在每个方块掉落后,你必须记录目前所有已经落稳的 方块堆叠的最高高度 。

        返回一个整数数组 ans ,其中 ans[i] 表示在第 i 块方块掉落后堆叠的最高高度。

        示例 1:

        输入:positions = [[1,2],[2,3],[6,1]]

        输出:[2,5,5]

        示例 2:

        输入:positions = [[100,100],[200,100]]

        输出:[100,100]
提示:

        1 <= positions.length <= 1000

        1 <= lefti <= 1e8

        1 <= sideLengthi <= 1e6


        这道题其实线段树并不是最优的解法但是是相对较易理解的解法。因为对于每次positions[i],我们可以看作在[poitions[i][0],positions[i][1]+position[i][0]-1]这个线段对positions[][i][1]进行累加。(为什么要减一之后会解释),那么这里线段树的数据域就变为了lazy和max,lazy代表其子树上的最大值都是lazy,max为该区间的最大值。

        插入和查询操作只需要在上一题代码的基础上做出修改即可,比如求取max的操作,这里应该不用解释了吧,就是对比根,左,右的最大求出一个最大。在查询的时候需要注意:lazy在从父结点继承过来的时候如果比当前根结点的lazy大,就要把当前根节点的lazy和max都更新成从父继承来的lazy。否则就会造成部分区间的懒惰标记没有继承而导致区间更新的时候没有带上父的状态。

        以上都是线段树的模板,现在来解释一些细节:

        (1):首先就是为什么查找和搜索区间的右端点都要减一,我们来考虑一下positions中的两个元素假如是:[2,3],[5,1],这样造成了边界重合,第一个区间代表在[2,5]这个区间上叠加高度为3的长方体,第二个区间代表在[5,6]这个区间上叠加高度为1的长方体,这时我们很自然的认为在5这个点会叠加,但是看下图就会明白了
在这里插入图片描述


        所以既然在边界不能叠加,那么就没必要在右端点也进行更新了,也就是说一个区间如果能跟之前的区间叠加的临界条件就是现区间的左端点恰好在之前某个区间右端点的前一点上。


        (2):然后就是这里我们进行累加不在搜索中进行,而是先找出该区间之前的最大值再加上本次区间的高度进行插入操作来进行累加。

class Solution {
    class TreeNode{
    public:
        TreeNode* left,*right;
        int lazy,max;
        TreeNode(){
            left=right=nullptr;
            lazy=max=0;
        }
    };
    TreeNode* root;
    void insert(TreeNode* & root,int l,int r,int start,int end,int lazy,int maxv){
        if(!root){
            root=new TreeNode();
        }
        if(lazy>root->lazy){
            root->lazy=lazy;
            root->max=lazy;
        }
        if(end<l||start>r){
            return;
        }
        if(start<=l&&r<=end){
            root->lazy=maxv;
            root->max=maxv;
            return;
        }
        int curl=root->lazy;
        root->lazy=0;
        int mid=l+((r-l)>>1);
        insert(root->left,l,mid,start,end,curl,maxv);
        insert(root->right,mid+1,r,start,end,curl,maxv);
        root->max=0;
        if(root->left){
            root->max=max(root->max,root->left->max);
        }
        if(root->right){
            root->max=max(root->max,root->right->max);
        }
    }
    void query(TreeNode* &root,int l,int r,int start,int end,int lazy,int & ans){
        if(!root){
            if(!lazy){
                return;
            }
            root=new TreeNode();
        }
       if(lazy>root->lazy){
            root->lazy=lazy;
            root->max=lazy;
        }
        if(end<l||r<start){
            return;
        }
        if(start<=l&&r<=end){
            ans=max(ans,root->max);
            return;
        }
        int curl=root->lazy;
        root->lazy=0;
        int mid=l+((r-l)>>1);
        query(root->left,l,mid,start,end,curl,ans);
        query(root->right,mid+1,r,start,end,curl,ans);
        root->max=0;
        if(root->left){
            root->max=max(root->max,root->left->max);
        }
        if(root->right){
            root->max=max(root->max,root->right->max);
        }
    }
public:
    vector<int> fallingSquares(vector<vector<int>>& positions) {
        vector<int> ans;
        ans.clear();
        root=nullptr;
        for(auto i:positions){
            int l=i[0];
            int r=i[1]+l-1;
            int ret=0;
            query(root,0,1000000000,l,r,0,ret);
            insert(root,0,1000000000,l,r,0,ret+i[1]);
            ans.push_back(root->max);
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值