题目:
一个音乐会总共有 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
// 总共只剩下 2 个座位。
提示:
1 <= n <= 5 * 104
1 <= m, k <= 109
0 <= maxRow <= n - 1
gather
和scatter
总 调用次数不超过5 * 104
次。
模拟
思路:
由于客户都偏向于落座低排,低索引,所以通过维护每一排可以做人的最小索引,进而对每一组客户落座进行模拟。
代码:
class BookMyShow {
public:
//vector<vector<bool>> seat;
vector<int> minIdx;
int n;
int m;
BookMyShow(int n, int m) {
//seat.resize(n, vector<bool>(m, false));
minIdx.resize(n, 0);
this->n = n;
this->m = m;
}
vector<int> gather(int k, int maxRow) {
vector<int> ans;
if (k > m) return ans;
for (int i = 0; i <= maxRow; i++) {
if (m - minIdx[i] >= k) {
ans.push_back(i);
ans.push_back(minIdx[i]);
minIdx[i] += k;
return ans;
}
}
return ans;
}
bool scatter(int k, int maxRow) {
vector<int> temp = minIdx;
bool flag = false;
for (int i = 0; i <= maxRow; i++) {
if (temp[i] < m) {
if (m - temp[i] >= k) {
// 这一排就可以直接完成所有人坐满
temp[i] += k;
flag = true;
break;
} else {
k -= (m - temp[i]);
temp[i] = m;
}
}
}
if (flag) minIdx = temp;
return flag;
}
};
/**
* 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);
*/
线段树
思路:
我们使用两个线段树 minTree 和 sumTree 分别维护区间 [l, r],即第 l 排到第 r 排座位内:
最小已坐座位数
已坐座位数之和
对于两种座位安排 gather 和 scatter,我们分开讨论:
gather 函数
根据题意,我们只需要找到区间 [0, maxRow] 内,已坐座位数 used ≤ m − k,且排数最小的那一排座位,然后返回安排的座位。
我们可以在 minTree 上进行二分搜索,从区间 [0, n − 1] 开始搜索,如果前半区间的最小已坐座位数 used 小于等于 m − k,那么继续搜索前半区间,否则搜索后半区间。令搜索到的座位排数为 i,对应的已坐座位数为 usedi ,如果 i > maxRow,那么说明无法安排座位,否则返回 i, usedi 为结果,同时修改第 i 排已坐座位数为 usedi + k。
scatter 函数
根据题意,我们首先需要获取区间 [0, maxRow] 内的所有剩余座位之和,如果小于 k,那么说明无法安排座位;否则,我们从第一排未坐满的排开始选择,直到座位全部安排完成。
通过 sumTree 可以获取区间 [0, maxRow] 内的所有已坐座位之和 usedTotal,那么剩余座位之和为 (maxRow + 1) * m − usedTotal。通过 minTree 可以二分搜索已坐座位数小于等于 m − 1 的最小座位排数,即第一排未坐满的排。然后按照上述步骤安排座位。
代码:
class BookMyShow {
private:
int n;
int m;
vector<int> minTree;
vector<long long> sumTree;
void modify(int i, int l, int r, int index, int val) {
if (l == r) {
minTree[i] = val;
sumTree[i] = val;
return;
}
int mid = (l + r) / 2;
if (index <= mid) {
modify(i * 2, l, mid, index, val);
} else {
modify(i * 2 + 1, mid + 1, r, index, val);
}
minTree[i] = min(minTree[i * 2], minTree[i * 2 + 1]);
sumTree[i] = sumTree[i * 2] + sumTree[i * 2 + 1];
}
int queryMinRow(int i, int l, int r, int val) {
if (l == r) {
if (minTree[i] > val) {
return n;
}
return l;
}
int mid = (l + r) / 2;
if (minTree[i * 2] <= val) {
return queryMinRow(i * 2, l, mid, val);
} else {
return queryMinRow(i * 2 + 1, mid + 1, r, val);
}
}
long long querySum(int i, int l, int r, int l2, int r2) {
if (l2 <= l && r <= r2) {
return sumTree[i];
}
int mid = (l + r) / 2;
long long sum = 0;
if (mid >= l2) {
sum += querySum(i * 2, l, mid, l2, r2);
}
if (mid + 1 <= r2) {
sum += querySum(i * 2 + 1, mid + 1, r, l2, r2);
}
return sum;
}
public:
BookMyShow(int n, int m): n(n), m(m), minTree(4 * n), sumTree(4 * n) {
}
vector<int> gather(int k, int maxRow) {
int i = queryMinRow(1, 0, n - 1, m - k);
if (i > maxRow) {
return {};
}
int used = querySum(1, 0, n - 1, i, i);
modify(1, 0, n - 1, i, used + k);
return {i, used};
}
bool scatter(int k, int maxRow) {
long long usedTotal = querySum(1, 0, n - 1, 0, maxRow);
if ((long long)(maxRow + 1) * m - usedTotal < k) {
return false;
}
int i = queryMinRow(1, 0, n - 1, m - 1);
while (true) {
long long used = querySum(1, 0, n - 1, i, i);
if (m - used >= k) {
modify(1, 0, n - 1, i, used + k);
break;
}
k -= m - used;
modify(1, 0, n - 1, i, m);
i++;
}
return true;
}
};