力扣(leetcode)每日一题 2286 以组为单位订音乐会的门票 | 线段树
2286. 以组为单位订音乐会的门票
题干
一个音乐会总共有 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
。
示例 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
gather
和scatter
总 调用次数不超过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]);
这些值的刷新,在递归前,还是递归后,还是递归方法的尽头。都是有讲究的