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

2024-09-28

题目:

一个音乐会总共有 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
                   // 总共只剩下 2 个座位。

提示:

  • 1 <= n <= 5 * 104
  • 1 <= m, k <= 109
  • 0 <= maxRow <= n - 1
  • gatherscatter 调用次数不超过 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;
    }
};