本文涉及知识点
线段树 最大值线段树
LeetCode2286. 以组为单位订音乐会的门票
一个音乐会总共有 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 次。
最大值线段树
第二种方式好解决:用向量记录各排剩余座位,并用start记录最小有剩余座位的排。本轮座位分配完毕后,start++。要判断能否满足,∑ x : 0 m a x R o w \sum_{x:0}^{maxRow}∑x:0maxRow 是否大于等于k。不能用前缀和,因为座位会变化,且不是一定从start开始减少,所以无法用前缀和。可用树状树状或线段树。
第一种方式,用最大值线段树。
先看[0,maxRow] 的最大值是否大于等于k,如果不是返回[]。
如果左半部分的最大值大于等于k,抛弃右半部分,不包括mid。
否则抛弃左半部分,包括mid。
故用左开右闭空间。
代码
核心代码
template<class ELE = int > class CTreeArr { public: CTreeArr(int iSize) :m_vData(iSize + 1) { } void Add(int index, ELE value) { index++; while (index < m_vData.size()) { m_vData[index] += value; index += index & (-index); } } ELE Sum(int index) { index++; ELE ret = 0; while (index) { ret += m_vData[index]; index -= index & (-index); } return ret; } ELE Get(int index) { return Sum(index) - Sum(index - 1); } private: vector<ELE> m_vData; }; template<class TSave, class TRecord, TRecord RecordNull = 0> class CLineTree { public: CLineTree(int iEleSize) :m_iEleSize(iEleSize), m_vArr(m_iEleSize * 4), m_vRecord(m_iEleSize * 4, RecordNull) { } void Update(int iLeftIndex, int iRightIndex, TRecord value) { Update(1, 1, m_iEleSize, iLeftIndex + 1, iRightIndex + 1, value); } template<class TGet> void Query(const TGet& oGet, int iLeftIndex, int iRightIndex) { Query(oGet, 1, 1, m_iEleSize, iLeftIndex + 1, iRightIndex + 1); } private: virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord) = 0; virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r) = 0; virtual void OnUpdate(TSave& save, const int& len, const TRecord& iUpdate) = 0; template<class TGet> void Query(const TGet& oGet, int iNode, int iSaveLeft, int iSaveRight, int iQueryLeft, int iQueryRight) { if ((iQueryLeft <= iSaveLeft) && (iQueryRight >= iSaveRight)) { oGet(m_vArr[iNode]); return; } Fresh(iNode, iSaveLeft, iSaveRight); const int iMid = iSaveLeft + (iSaveRight - iSaveLeft) / 2; if (iMid >= iQueryLeft) { Query(oGet, iNode * 2, iSaveLeft, iMid, iQueryLeft, iQueryRight); } if (iMid + 1 <= iQueryRight) { Query(oGet, iNode * 2 + 1, iMid + 1, iSaveRight, iQueryLeft, iQueryRight); } } void Update(int iNode, int iSaveLeft, int iSaveRight, int iOpeLeft, int iOpeRight, TRecord value) { if (iNode >= m_vArr.size()) { return; } if ((iOpeLeft <= iSaveLeft) && (iOpeRight >= iSaveRight)) { OnUpdate(m_vArr[iNode], min(iSaveRight, iOpeRight) - max(iSaveLeft, iOpeLeft) + 1, value); OnUpdateRecord(m_vRecord[iNode], value); return; } Fresh(iNode, iSaveLeft, iSaveRight); const int iMid = iSaveLeft + (iSaveRight - iSaveLeft) / 2; if (iMid >= iOpeLeft) { Update(iNode * 2, iSaveLeft, iMid, iOpeLeft, iOpeRight, value); } if (iMid + 1 <= iOpeRight) { Update(iNode * 2 + 1, iMid + 1, iSaveRight, iOpeLeft, iOpeRight, value); } // 如果有后代,至少两个后代 OnUpdateParent(m_vArr[iNode], m_vArr[iNode * 2], m_vArr[iNode * 2 + 1]); } void Fresh(int iNode, int iDataLeft, int iDataRight) { if (RecordNull == m_vRecord[iNode]) { return; } const int iMid = iDataLeft + (iDataRight - iDataLeft) / 2; Update(iNode * 2, iDataLeft, iMid, iDataLeft, iMid, m_vRecord[iNode]); Update(iNode * 2 + 1, iMid + 1, iDataRight, iMid + 1, iDataRight, m_vRecord[iNode]); m_vRecord[iNode] = RecordNull; } const int m_iEleSize; vector<TSave> m_vArr; vector<TRecord> m_vRecord; }; template<class TSave, class TRecord, TRecord RecordNull = 0> class CMaxLineTree : public CLineTree<TSave, TRecord, RecordNull> { using CLineTree< TSave, TRecord, RecordNull>::CLineTree; virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord) override { old = newRecord; } virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r) override { par = max(left, r); } virtual void OnUpdate(TSave& save, const int& len, const TRecord& iUpdate) override { save = iUpdate; } }; class BookMyShow { public: BookMyShow(int n, int m):m_lineTree(n), m_treeArr(n), m_iM(m){ for (int i = 0; i < n; i++) { m_treeArr.Add(i, m); } m_lineTree.Update(0, n - 1,m); } vector<int> gather(int k, int maxRow) { int iMax = 0; auto Get = [&iMax](int value) { iMax = max(iMax, value); }; m_lineTree.Query(Get, 0, maxRow); if (iMax < k) { return {}; } int left = -1, r = maxRow; while (r - left > 1) { const int mid = left + (r - left) / 2; iMax = 0; m_lineTree.Query(Get, 0, mid); if (iMax < k) { left = mid; } else { r = mid; } } const int cur = m_treeArr.Get(r); m_lineTree.Update(r, r, cur - k); m_treeArr.Add(r, -k); return { r,m_iM - cur }; } bool scatter(int k, int maxRow) { if (m_treeArr.Sum(maxRow) < k) { return false; } while (k > 0) { const int cur = (int)m_treeArr.Get(m_iStart); const int use = min(cur, k); k -= use; m_treeArr.Add(m_iStart, -use); m_lineTree.Update(m_iStart, m_iStart, cur - use); if (cur == use) { m_iStart++; } } return true; } CMaxLineTree<int,int,-1> m_lineTree; CTreeArr<long long> m_treeArr; int m_iStart = 0; const int m_iM; };
2023年3月
class CLineTree { public: CLineTree(int iArrSize) :m_iArrSize(iArrSize), m_vData(iArrSize * 4) {
} //iIndex 从0开始 void Modify( int iIndex, int iValue) { Modify(1, 1, m_iArrSize, iIndex + 1, iValue); } //iNeedQueryLeft iNeedQueryRight 从0开始 int Query(const int iNeedQueryLeft, const int iNeedQueryRight) { return Query(1, 1, m_iArrSize, iNeedQueryLeft + 1, iNeedQueryRight + 1); }
protected: int Query(const int iTreeNodeIndex, const int iRecordLeft, const int iRecordRight, const int iNeedQueryLeft, const int iNeedQueryRight) { if ((iNeedQueryLeft <= iRecordLeft) && (iNeedQueryRight >= iRecordRight)) { return m_vData[iTreeNodeIndex]; } const int iMid = (iRecordLeft + iRecordRight) / 2; int iRet = 0; if (iNeedQueryLeft <= iMid) { iRet = Query(iTreeNodeIndex * 2, iRecordLeft, iMid, iNeedQueryLeft, iNeedQueryRight); } if (iNeedQueryRight > iMid) { iRet = max(iRet, Query(iTreeNodeIndex * 2 + 1, iMid + 1, iRecordRight, iNeedQueryLeft, iNeedQueryRight)); } return iRet; } void Modify(int iTreeNodeIndex, int iLeft, int iRight, int iIndex, int iValue) { if (iLeft == iRight) { m_vData[iTreeNodeIndex] = iValue; return; } const int iMid = (iLeft + iRight) / 2; if (iIndex <= iMid) { Modify(iTreeNodeIndex * 2, iLeft, iMid, iIndex, iValue); } else { Modify(iTreeNodeIndex * 2 + 1, iMid + 1, iRight, iIndex, iValue); } m_vData[iTreeNodeIndex] = max(m_vData[iTreeNodeIndex * 2], m_vData[iTreeNodeIndex * 2 + 1]); } const int m_iArrSize; std::vector m_vData; }; template class CTreeArr { public: CTreeArr(int iSize) :m_vData(iSize+1) {
} void Add(int index, T value) { index++; while (index < m_vData.size()) { m_vData[index] += value; index += index&(-index); } } T Sum(int index) { index++; T ret = 0; while (index ) { ret += m_vData[index]; index -= index&(-index); } return ret; }
private: vector m_vData; }; class BookMyShow { public: BookMyShow(int n, int m) :m_r(n), m_c(m), m_maxLineTree(m_r), m_sumTreeArr(m_r) { m_vCanBuy.assign(m_r,m_c); for (int i = 0; i < m_r; i++) { m_maxLineTree.Modify(i, m_c); m_sumTreeArr.Add(i, m_c); } } vector gather(int k, int maxRow) { if (m_maxLineTree.Query(0, maxRow) < k) { return vector(); } int left = -1, right = maxRow ; while (right > left + 1) { int iMid = left + (right - left) / 2; if (m_maxLineTree.Query(0, iMid) < k ) { left = iMid; } else { right = iMid; } } vector vRet; vRet.push_back(right); vRet.push_back(m_c - m_vCanBuy[right]); Sell(right, k); Fresh(); return vRet; } bool scatter(int k, int maxRow) { if (m_sumTreeArr.Sum(maxRow) < k ) { return false; } for (int i = m_iPreSellOutRowNum; k > 0; i++) { const int iSell = min(k, m_vCanBuy[i]); Sell(i, iSell); k -= iSell; } Fresh(); return true; } void Sell(int r,int iNum) { m_vCanBuy[r] -= iNum; m_maxLineTree.Modify(r , m_vCanBuy[r]); m_sumTreeArr.Add(r,-iNum); } void Fresh() { while ((m_iPreSellOutRowNum < m_r) && (0 == m_vCanBuy[m_iPreSellOutRowNum])) { m_iPreSellOutRowNum++; } } const int m_r, m_c; vector m_vCanBuy;//各行剩余的票数,由于从左向右卖。所以一定剩下最右边的。 CLineTree m_maxLineTree;//记录各行,最大值 CTreeArr m_sumTreeArr;//方便求空闲座位之后 int m_iPreSellOutRowNum =0;//[0,m_iSellOutRow)行已经卖光 };
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。