力扣(leetcode)每日一题 2286 以组为单位订音乐会的门票 | 线段树

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

题干

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

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

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

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

请你实现 BookMyShow 类:

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

示例 1:

输入:
[“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
  • gatherscatter 调用次数不超过 5 * 104 次。
题解1

这里实现的时候,我还不会线段树。硬是通过小优化使得提交通过

在这里插入图片描述

class BookMyShow {
     int totalRow;
    int[] arr;
    int jump = 0;
    long totalCount;

    public BookMyShow (int n, int m) {
        // 需要加快更新的频率
        arr = new int[n];
        totalRow = m;
        totalCount = (long) n * m;
    }

    public int[] gather(int k, int maxRow) {
        // 快速失败
        if (k > totalRow) {
            return new int[0];
        }

        for (int i = jump; i <= maxRow; i++) {
            // 计算是否放得下
            if (arr[i] + k <= totalRow) {
                int[] res = new int[]{i, arr[i]};
                arr[i] = arr[i] + k;
                // 没有问题
                totalCount -= k;
                return res;
            }
        }
        return new int[0];
    }

    public boolean scatter(int k, int maxRow) {
        if (totalCount < k) {
            return false;
        }
        int count = 0;
        boolean flag = false;
        int index = 0;
        for (int i = jump; i <= maxRow; i++) {
            count += totalRow - arr[i];
            if (count >= k) {
                flag = true;
                index = i;
                break;
            }
        }
        if (!flag) {
            return false;
        }
        // 可以省略
//        for (int i = jump; i < index; i++) {
//            arr[i] = totalRow;
//        }
        jump = index;
        // 多累计了3人,需要减去
        arr[index] = totalRow - (count - k);
        totalCount -= k;
        return true;
    }
}

题解2

这里的线段树应该不是最优解,不然这里的数值不会这么难看
但是我没有精力去精进
因为用前面题解的模板来改写
在这里插入图片描述

class Solution2286 {  
    public static void main(String[] args) {  
        BookMyShow bookMyShow = new BookMyShow(5, 9); // 假设是9个位置  
        bookMyShow.scatter(9, 1);  
        bookMyShow.scatter(1, 3);  
        bookMyShow.gather(3, 4);  
        bookMyShow.gather(1, 1);  
        bookMyShow.gather(10, 4);  
    }  
  
    static class BookMyShow {  
        int totalSpace;  
        int[] arr;  
        long[] sumArr;  
        int[] minArr;  
        int right;  
        int left = 1;  
        int root = 1;  
  
        public BookMyShow(int n, int m) {  
            right = n;  
            int length = n + 1;  
            arr = new int[length];  
            sumArr = new long[4 * length]; // 这里加速了范围上累加和的查询  
            minArr = new int[4 * length];  // 这里加速了范围上最小值的查询  
            totalSpace = m;  
        }  
  
  
        public int[] gather(int k, int maxRow) {  
            maxRow++;  
            int min = getMin(left, right, root, totalSpace - k);//  9 个位置 来了5个  需要找小于等于4的  
            if (min > maxRow) {  
                return new int[0];  
            }  
            // 查询当前min的值  
            long query = query(min, min);  
            // 更新当前min的值  
            update(min, min, (int) query + k);  
            return new int[]{min - 1, (int) query};  
        }  
  
        //  9871    9   只要找到小于9的行就可以进行增加了  
        public boolean scatter(int k, int maxRow) {  
            maxRow++;  
            // 计算和  
            long res = query(1, maxRow);  
            // 当这里的累计大于k的时候就可以入座  
            if (res + k > (long) (maxRow) * totalSpace) {  
                return false;  
            }  
            //  9 个位置 只要是小于9的就可以了,查询小于等于8的  
            int min = getMin(left, right, root, totalSpace - 1);  
            while (true) {  
                int query = (int) query(min, min);  
                if (query + k <= totalSpace) { // 当前排的位置可以把人安排了就安排入座  
                    update(min, min, k + query); // 这里需要k+min  
                    return true;  
                }  
                // 更新  
                update(min, min, totalSpace);  
                k -= (totalSpace - query);// 这里查询到4个,那么可以安排 5个入座。计算就是9-4  
                min = getMin(left, right, root, totalSpace - 1);  
            }  
        }  
  
        public int getMin(int left, int right, int node, int value) {  
            if (left == right) {  
                if (minArr[node] > value) {  
                    // 没有  
                    return Integer.MAX_VALUE;  
                }  
                return left;  
            }  
            int mid = (left + right) / 2;  
            if (minArr[node * 2] <= value) {  
                return getMin(left, mid, node * 2, value);  
            } else {  
                return getMin(mid + 1, right, node * 2 + 1, value);  
            }  
        }  
  
        public long query(int start, int end) {  
            return query(start, end, left, right, root);  
        }  
  
        public long query(int start, int end, int left, int right, int node) {  
            if (start <= left && right <= end) {  
                return sumArr[node];  
            }  
            int mid = (left + right) / 2;  
            long total = 0;  
            if (start <= mid) { // 下发左边  
                total += query(start, end, left, mid, node * 2);  
            }  
            if (end > mid) { // 下发右边  
                total += query(start, end, mid + 1, right, node * 2 + 1);  
            }  
            return total;  
        }  
  
        public void update(int start, int end, int value) {  
            update(start, end, value, left, right, root);  
        }  
  

		// 模板是范围上更新,但是这里的实际情况是单点更新
        public void update(int start, int end, int value, int left, int right, int node) {  
            if (start <= left && right <= end) {  
                sumArr[node] = value;  
                minArr[node] = value;  
                return;  
            }  
            int mid = (left + right) / 2;  
            if (start <= mid) { // 下发左边  
                update(start, end, value, left, mid, node * 2);  
            }  
            if (end > mid) { // 下发右边  
                update(start, end, value, mid + 1, right, node * 2 + 1);  
            }  
            // 刷新求和  
            sumArr[node] = sumArr[node * 2] + sumArr[node * 2 + 1];  
            // 刷新最小  
            minArr[node] = Math.min(minArr[node * 2], minArr[node * 2 + 1]);  
        }  
  
    }  
  
}
总结

minArr的使用是超出了模板的范围

// 刷新求和  
sumArr[node] = sumArr[node * 2] + sumArr[node * 2 + 1];  
// 刷新最小  
minArr[node] = Math.min(minArr[node * 2], minArr[node * 2 + 1]);  

这些值的刷新,在递归前,还是递归后,还是递归方法的尽头。都是有讲究的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值