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]);
这些值的刷新,在递归前,还是递归后,还是递归方法的尽头。都是有讲究的