#力扣每日一题day2-9.28

以组为单位订音乐会的门票

代码来源

2024.9.28 每日一题 2286 [线段树] 很久没遇到了_哔哩哔哩_bilibili

题目

一个音乐会总共有 n 排座位,编号从 0 到 n - 1 ,每一排有 m 个座椅,编号为 0 到 m - 1 。你需要设计一个买票系统,针对以下情况进行座位安排:

  • 同一组的 k 位观众坐在 同一排座位,且座位连续 
  • k 位观众中 每一位 都有座位坐,但他们 不一定 坐在一起。

由于观众非常挑剔,所以:

  • 只有当一个组里所有成员座位的排数都 小于等于 maxRow ,这个组才能订座位。每一组的 maxRow 可能 不同 。
  • 如果有多排座位可以选择,优先选择 最小 的排数。如果同一排中有多个座位可以坐,优先选择号码 最小 的。

请你实现 BookMyShow 类:

  • BookMyShow(int n, int m) ,初始化对象,n 是排数,m 是每一排的座位数。
  • int[] gather(int k, int maxRow) 返回长度为 2 的数组,表示 k 个成员中 第一个座位 的排数和座位编号,这 k 位成员必须坐在 同一排座位,且座位连续 。换言之,返回最小可能的 r 和 c 满足第 r 排中 [c, c + k - 1] 的座位都是空的,且 r <= maxRow 。如果 无法 安排座位,返回 [] 。
  • boolean scatter(int k, int maxRow) 如果组里所有 k 个成员 不一定 要坐在一起的前提下,都能在第 0 排到第 maxRow 排之间找到座位,那么请返回 true 。这种情况下,每个成员都优先找排数 最小 ,然后是座位编号最小的座位。如果不能安排所有 k 个成员的座位,请返回 false 。

示例

输入:
["BookMyShow", "gather", "gather", "scatter", "scatter"]
[[2, 5], [4, 0], [2, 0], [5, 1], [5, 1]]
输出:
[null, [0, 0], [], true, false]

解释:
BookMyShow bms = new BookMyShow(2, 5); // 总共有 2 排,每排 5 个座位。
bms.gather(4, 0); // 返回 [0, 0]
                  // 这一组安排第 0 排 [0, 3] 的座位。
bms.gather(2, 0); // 返回 []
                  // 第 0 排只剩下 1 个座位。
                  // 所以无法安排 2 个连续座位。
bms.scatter(5, 1); // 返回 True
                   // 这一组安排第 0 排第 4 个座位和第 1 排 [0, 3] 的座位。
bms.scatter(5, 1); // 返回 False
                   // 总共只剩下 1 个座位。

提示

  • 1 <= n <= 5 * 104
  • 1 <= m, k <= 109
  • 0 <= maxRow <= n - 1
  • gather 和 scatter  调用次数不超过 5 * 104 次。

知识点

1、线段树、二分查找

2、c++动态数组vector的声明

        vector<long long> sum;       

        sum.resize(4*n);

3、注意数据范围,此题有些地方要开Long Long

代码

class BookMyShow {
    int n;
    int m;
    vector<long long> sum;
    vector<int> used;
public:

    BookMyShow(int n, int m) {
        this->n=n;
        this->m = m;
        sum.resize(4*n);
        used.resize(4*n);
    }
    
    vector<int> gather(int k, int maxRow) {
        //根节点是闭区间,目标范围是半闭区间
        int r = FindFirst(1,1,n,1,maxRow+1,m-k);   //从根节点出发,下标从1到n;m-k表示该桶的最多使用量
        if(r<1) return {};

        int c=QuerySum(1,1,n,r,r);
        Modify(1,1,n,r,k);  //往第r个桶加入k个量
        
        return {r-1,c};
    }
    
    bool scatter(int k, int maxRow) {
        long s = QuerySum(1,1,n,1,maxRow+1);
        if(1LL * (maxRow+1)*m - s < k) return false;
        else{
            for(int i=FindFirst(1,1,n,1,maxRow+1,m-1);k>0;i++){
                int left = (int)m-QuerySum(1,1,n,i,i);
                Modify(1,1,n,i,min(left,k));
                k-=left;
            }
            return true;
        }
    }

    void Modify(long long o, int l, int r, int idx, int val){
        if(l==r){
            sum[o]+=val;
            used[o]+=val;
            return;
        }

        //递归更新左右子树
        int m=l+(r-l)/2;
        if(idx<=m) Modify(o*2,l,m,idx,val);
        else Modify(o*2+1,m+1,r,idx,val);

        //向上更新父节点
        used[o] = min(used[o*2],used[o*2+1]);
        sum[o] = sum[o*2]+sum[o*2+1];
    }

    long long QuerySum(int o, int l, int r, int L,int R){
        if(L<=l && R>=r) return sum[o];

        long long res=0;
        int m = l+(r-l)/2;
        if(L<=m) res+=QuerySum(o*2,l,m,L,R);
        if(m<R) res+= QuerySum(o*2+1,m+1,r,L,R);

        return res;
    }

    // val是被使用的最多数量
    // used[]维护被使用的最小值
    int FindFirst(int o, int l, int r, int L, int R, int val){
        if(used[o]>val) return 0;
        if(l==r) return l;

        int m = l+(r-l)/2;
        if(used[o*2]<=val) return FindFirst(o*2,l,m,L,R,val);
        if(m<R) return FindFirst(o*2+1,m+1,r,L,R,val);    //要在查询区间内

        return 0;
    }
};

/**
 * Your BookMyShow object will be instantiated and called as such:
 * BookMyShow* obj = new BookMyShow(n, m);
 * vector<int> param_1 = obj->gather(k,maxRow);
 * bool param_2 = obj->scatter(k,maxRow);
 */

/**
区间标记和区间查询——线段树
区间修改——>单点修改——因为每排占位是联系的

*/

反思

还没有完全弄懂,但有以下收获

线段树的基本原理

1、线段树是一个二叉树,每个节点存储的是某个区间的统计值(比如区间和)

2、叶子节点存储的是数组中的某个具体元素

3、内部节点存储的是其左右子树的区间和,即区间[l,r]的和。根节点存储整个数组的和。

关于代码中的函数

1、关于FindFirst函数为什么最后return l的条件是(l==r),当l==r时,代表找到了叶子节点(即某个具体的排)满足题目条件。

这种递归查找模式常用于线段树的区间查询或定位操作,保证找到的是最符合条件的具体位置。

2、QuerySum函数是一个在线段树中进行区间求和的函数,利用了分治的思想来查询某个区间[L,R]内的元素和。

o表示当前线段树节点的编号,这里假设从1开始

[l,r]表示节点o管理的区间范围

[L,R]表示我们要查询的区间范围

        分治思想

        若当前节点管理的区间和查询的区间部分重叠,我们需要将当前区间划分为左右两个子区间,递归查询左右子区间的和。

3、为什么modify需要递归。

线段树的每个节点的值都依赖于它的左右子节点的值,因此每次修改一个叶子节点的值,必须递归更新沿途的所有节点,确保整个线段树中的区间和保持正确。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值