资讯动态

力扣 2286「预订音乐会门票」题解:线段树二分 + 区间最小/和双维护,从水桶模型到 codeforces-go 模板库源码印证

发布时间:2026/10/9 1:44:44 来源:尧图企业网站定制
科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载导读本文以「力扣第 2286 题 Booking Concert Tickets in Groups预订音乐会门票」的官方题解为主体将其抽象为n 个容量为 m 升的空水桶的等价模型系统讲解如何用一棵同时维护区间接水量最小值 min 与区间接水量之和 sum 的线段树在 $\mathcal{O}(\log n)$ 时间内完成gather成组预订与均摊 $\mathcal{O}(\log n)$ 的scatter分散预订。文章完整继承原题解的 Python/Java/C/C/Go/JavaScript/Rust 七份实现此处完整呈现六种核心语言并结合当前仓库 copypasta/segment_tree.go 中线段树二分通用模板findFirst/findLast与$2n$ 空间注释从源码层面印证该解法的底层原理与工程细节。读完本文你将掌握区间最值 区间和复合线段树的构建、单点增量更新、前缀区间和查询以及线段树二分在区间内二分查找第一个满足条件的位置这一高频竞赛技巧并能直接迁移到任意区间内找第一个可行位置类问题。一、题目重述换一个场景本质不变原题是音乐会场馆有 $n$ 个座位排、每排 $m$ 个座位官方题解将其等价改造为更直观的水桶模型一开始有 $n$ 个空水桶每个水桶容量都是 $m$ 升编号 $0$ 到 $n-1$。gather(k, maxRow)在前 $\textit{maxRow}$ 个水桶下标 $[0,\textit{maxRow}]$中找第一个还能装至少 $k$ 升水的水桶倒入 $k$ 升水。若存在返回[水桶编号, 倒水前的接水量]否则返回空列表。scatter(k, maxRow)往 $[0,\textit{maxRow}]$ 内倒入总量为 $k$ 升的水从左到右依次选择未装满的水桶。若总量放不下则不执行任何操作并返回false否则执行并返回true。注意scatter与gather的语义差别gather要求单个水桶剩余容量 $\ge k$scatter只要求总量放得下可以拆散到多个水桶且操作是要么全倒、要么不倒的原子语义。二、思路剖析一个水桶两种维护需求把接水量记作每个位置的元素值初始全为 0容量 $m$ 即接水量上限。我们需要支持找第一个剩余容量 $\ge k$ 的水桶剩余容量 $ m - \text{接水量}$等价于找接水量 $\le m-k$的最靠左位置——这是区间最小值上的二分搜索维护每个水桶的接水量单点增量修改维护前缀 $[0,\textit{maxRow}]$ 的接水量之和用于快速判断scatter是否放得下——这是区间和查询。因此用一棵节点同时保存min区间接水量最小值与sum区间接水量之和的线段树即可全部解决。本题只有单点修改、没有区间更新无需懒标记代码可以写得非常精简。2.1 gather线段树二分找第一个可行桶从根节点递归若当前区间min m-k整个区间每个桶剩余容量都 $k$无法倒入返回 $0$哨兵表示不可行若当前区间长度为 1l r返回区间端点即找到目标水桶若左半区间min m-k答案必在左半区间递归左半否则若 $\textit{maxRow}$ 落在右半区间内递归右半否则返回 $-1$表示 $[0,\textit{maxRow}]$ 内没有这样的水桶。每次只走左或右一支沿树高下探时间复杂度 $\mathcal{O}(\log n)$。这个过程就是线段树二分不需要先二分位置再查区间而是直接用节点上维护的聚合值这里是min在树内定向游走一步到位。找到桶 $r$ 后querySum单点查出倒水前接水量 $c$再update将该桶接水量增加 $k$返回[r, c]。2.2 scatter先判总量再从左到右贪心倒水可行性判断若 $[0,\textit{maxRow}]$ 的接水量之和 $s m\cdot(\textit{maxRow}1)-k$即剩余总容量 $ k$则无法执行直接返回false同时保证不执行任何操作的原子性。执行倒水从第一个未装满接水量 $\le m-1$的水桶开始——这同样是一次findFirst(1, maxRow, m-1)线段树二分。随后进入循环每次对当前桶 $i$left min(m - 单点接水量, k) // 本次实际倒入量 update(i, left) // 倒水 k - left; i // 剩余量减少指针右移由于可行性已提前判过循环必然在 $[0,\textit{maxRow}]$ 内把 $k$ 倒完。2.3 scatter 的均摊复杂度为什么可以放心 whilescatter单次最坏看似 $\mathcal{O}(\textit{maxRow} \cdot \log n)$但整体分析可摊还装满的水桶后面不会再被遍历指针i单调递增因此所有scatter循环的总次数为 $\mathcal{O}(nq)$$q$ 为scatter调用次数总时间复杂度为 $\mathcal{O}((nq)\log n)$若近似认为 $nq$则单次均摊复杂度为 $\mathcal{O}(\log n)$。这是本题最值得品味的复杂度论证。三、线段树数组大小$2n$ 空间还是 $4n$——从模板库源码找答案传统的线段树实现习惯开4n大小但本题官方题解各语言版本都采用了更省的写法Python/Java/C/JavaScript/Rustsize 2 (32 - clz(n))C 用4 __lg(n)Gomake(seg, 2bits.Len(uint(n-1)))。其原理是设 $h\lceil\log_2 n\rceil$完全二叉树下标范围正好是 $[1, 2^{h1}-1]$数组开2 h即可覆盖所有节点比4n更小。仓库 copypasta/segment_tree.go 的开头注释对此有非常详尽的记录包括区间 [1,1] 对应的节点编号为1bits.Len(uint(n-1))当 $n2^k-1$ 时此时只需要 $2n$ 的空间空间最省情形并附有一张 $i/n$ 与节点编号的对照表如 $n18$ 时 $i/n\approx 2.7222$、$n36$ 时首次超过 $3n$用来精确估算各种 $n$ 下的真实占用防止 Hack 数据把线段树打爆。库内通用构造函数 newSegmentTree 正是用t : make(seg, 2bits.Len(uint(n-1)))创建数组与本题 Go 解完全一致可对照阅读。四、复杂度分析官方结论时间复杂度初始化build$\mathcal{O}(n)$gather$\mathcal{O}(\log n)$。由于每次只递归左半或右半区间线段树二分的耗时等于树高 $\mathcal{O}(\log n)$scatter总体来看装满的水桶不再被遍历所有scatter的循环次数之和为 $\mathcal{O}(nq)$$q$ 为scatter调用次数因此总时间复杂度为 $\mathcal{O}((nq)\log n)$若 $nq$单次均摊 $\mathcal{O}(\log n)$。空间复杂度$\mathcal{O}(n)$线段树本身 $\mathcal{O}(n)$ 个节点。五、完整实现六种语言逐行对照以下代码为官方题解原版可直接提交力扣 2286。Python3class BookMyShow: def __init__(self, n: int, m: int): self.n n self.m m self.min [0] * (2 n.bit_length()) # 相比 4n 空间更小 self.sum [0] * (2 n.bit_length()) # 线段树把下标 i 上的元素值增加 val def update(self, o: int, l: int, r: int, i: int, val: int) - None: if l r: self.min[o] val self.sum[o] val return m (l r) // 2 if i m: self.update(o * 2, l, m, i, val) else: self.update(o * 2 1, m 1, r, i, val) self.min[o] min(self.min[o * 2], self.min[o * 2 1]) self.sum[o] self.sum[o * 2] self.sum[o * 2 1] # 线段树返回区间 [L,R] 内的元素和 def query_sum(self, o: int, l: int, r: int, L: int, R: int) - int: if L l and r R: return self.sum[o] res 0 m (l r) // 2 if L m: res self.query_sum(o * 2, l, m, L, R) if R m: res self.query_sum(o * 2 1, m 1, r, L, R) return res # 线段树返回区间 [0,R] 中 val 的最靠左的位置不存在时返回 -1 def find_first(self, o: int, l: int, r: int, R: int, val: int) - int: if self.min[o] val: return -1 # 整个区间的元素值都大于 val if l r: return l m (l r) // 2 if self.min[o * 2] val: return self.find_first(o * 2, l, m, R, val) if R m: return self.find_first(o * 2 1, m 1, r, R, val) return -1 def gather(self, k: int, maxRow: int) - List[int]: # 找第一个能倒入 k 升水的水桶 r self.find_first(1, 0, self.n - 1, maxRow, self.m - k) if r 0: # 没有这样的水桶 return [] c self.query_sum(1, 0, self.n - 1, r, r) self.update(1, 0, self.n - 1, r, k) # 倒水 return [r, c] def scatter(self, k: int, maxRow: int) - bool: # [0,maxRow] 的接水量之和 s self.query_sum(1, 0, self.n - 1, 0, maxRow) if s self.m * (maxRow 1) - k: return False # 水桶已经装了太多的水 # 从第一个没有装满的水桶开始 i self.find_first(1, 0, self.n - 1, maxRow, self.m - 1) while k: left min(self.m - self.query_sum(1, 0, self.n - 1, i, i), k) self.update(1, 0, self.n - 1, i, left) # 倒水 k - left i 1 return TrueJavaclass BookMyShow { private int n; private int m; private int[] min; private long[] sum; public BookMyShow(int n, int m) { this.n n; this.m m; int size 2 (32 - Integer.numberOfLeadingZeros(n)); // 比 4n 更小 min new int[size]; sum new long[size]; } public int[] gather(int k, int maxRow) { // 找第一个能倒入 k 升水的水桶 int r findFirst(1, 0, n - 1, maxRow, m - k); if (r 0) { // 没有这样的水桶 return new int[]{}; } int c (int) querySum(1, 0, n - 1, r, r); update(1, 0, n - 1, r, k); // 倒水 return new int[]{r, c}; } public boolean scatter(int k, int maxRow) { // [0,maxRow] 的接水量之和 long s querySum(1, 0, n - 1, 0, maxRow); if (s (long) m * (maxRow 1) - k) { return false; // 水桶已经装了太多的水 } // 从第一个没有装满的水桶开始 int i findFirst(1, 0, n - 1, maxRow, m - 1); while (k 0) { int left Math.min(m - (int) querySum(1, 0, n - 1, i, i), k); update(1, 0, n - 1, i, left); // 倒水 k - left; i; } return true; } // 把下标 i 上的元素值增加 val private void update(int o, int l, int r, int i, int val) { if (l r) { min[o] val; sum[o] val; return; } int m (l r) / 2; if (i m) { update(o * 2, l, m, i, val); } else { update(o * 2 1, m 1, r, i, val); } min[o] Math.min(min[o * 2], min[o * 2 1]); sum[o] sum[o * 2] sum[o * 2 1]; } // 返回区间 [L,R] 内的元素和 private long querySum(int o, int l, int r, int L, int R) { if (L l r R) { return sum[o]; } long res 0; int m (l r) / 2; if (L m) { res querySum(o * 2, l, m, L, R); } if (R m) { res querySum(o * 2 1, m 1, r, L, R); } return res; } // 返回区间 [0,R] 中 val 的最靠左的位置不存在时返回 -1 private int findFirst(int o, int l, int r, int R, int val) { if (min[o] val) { return -1; // 整个区间的元素值都大于 val } if (l r) { return l; } int m (l r) / 2; if (min[o * 2] val) { return findFirst(o * 2, l, m, R, val); } if (R m) { return findFirst(o * 2 1, m 1, r, R, val); } return -1; } }Cclass BookMyShow { int n, m; vectorint mn; vectorlong long sum; // 把下标 i 上的元素值增加 val void update(int o, int l, int r, int i, int val) { if (l r) { mn[o] val; sum[o] val; return; } int m (l r) / 2; if (i m) { update(o * 2, l, m, i, val); } else { update(o * 2 1, m 1, r, i, val); } mn[o] min(mn[o * 2], mn[o * 2 1]); sum[o] sum[o * 2] sum[o * 2 1]; } // 返回区间 [L,R] 内的元素和 long long querySum(int o, int l, int r, int L, int R) { if (L l r R) { return sum[o]; } long long res 0; int m (l r) / 2; if (L m) { res querySum(o * 2, l, m, L, R); } if (R m) { res querySum(o * 2 1, m 1, r, L, R); } return res; } // 返回区间 [0,R] 中 val 的最靠左的位置不存在时返回 -1 int findFirst(int o, int l, int r, int R, int val) { if (mn[o] val) { return -1; // 整个区间的元素值都大于 val } if (l r) { return l; } int m (l r) / 2; if (mn[o * 2] val) { return findFirst(o * 2, l, m, R, val); } if (R m) { return findFirst(o * 2 1, m 1, r, R, val); } return -1; } public: BookMyShow(int n, int m) : n(n), m(m), mn(4 __lg(n)), sum(4 __lg(n)) {} vectorint gather(int k, int maxRow) { // 找第一个能倒入 k 升水的水桶 int r findFirst(1, 0, n - 1, maxRow, m - k); if (r 0) { // 没有这样的水桶 return {}; } int c querySum(1, 0, n - 1, r, r); update(1, 0, n - 1, r, k); // 倒水 return {r, c}; } bool scatter(int k, int maxRow) { // [0,maxRow] 的接水量之和 long long s querySum(1, 0, n - 1, 0, maxRow); if (s (long long) m * (maxRow 1) - k) { return false; // 水桶已经装了太多的水 } // 从第一个没有装满的水桶开始 int i findFirst(1, 0, n - 1, maxRow, m - 1); while (k) { int left min(m - (int) querySum(1, 0, n - 1, i, i), k); update(1, 0, n - 1, i, left); // 倒水 k - left; i; } return true; } };Go与仓库 leetcode/biweekly/79/d/d.go 完全一致type seg []struct{ l, r, min, sum int } func (t seg) build(o, l, r int) { t[o].l, t[o].r l, r if l r { return } m : (l r) 1 t.build(o1, l, m) t.build(o1|1, m1, r) } // 把下标 i 上的元素值增加 val func (t seg) update(o, i, val int) { if t[o].l t[o].r { t[o].min val t[o].sum val return } m : (t[o].l t[o].r) 1 if i m { t.update(o1, i, val) } else { t.update(o1|1, i, val) } lo, ro : t[o1], t[o1|1] t[o].min min(lo.min, ro.min) t[o].sum lo.sum ro.sum } // 返回区间 [l,r] 内的元素和 func (t seg) querySum(o, l, r int) (sum int) { if l t[o].l t[o].r r { return t[o].sum } m : (t[o].l t[o].r) 1 if l m { sum t.querySum(o1, l, r) } if r m { sum t.querySum(o1|1, l, r) } return } // 返回区间 [0,r] 中 val 的最靠左的位置不存在时返回 -1 func (t seg) findFirst(o, r, val int) int { if t[o].min val { return -1 // 整个区间的元素值都大于 val } if t[o].l t[o].r { return t[o].l } m : (t[o].l t[o].r) / 2 if t[o*2].min val { return t.findFirst(o*2, r, val) } if r m { return t.findFirst(o*21, r, val) } return -1 } type BookMyShow struct { seg n, m int } func Constructor(n, m int) BookMyShow { t : make(seg, 2bits.Len(uint(n-1))) // 比 4n 更小 t.build(1, 0, n-1) return BookMyShow{t, n, m} } func (t *BookMyShow) Gather(k, maxRow int) []int { // 找第一个能倒入 k 升水的水桶 r : t.findFirst(1, maxRow, t.m-k) if r 0 { // 没有这样的水桶 return nil } c : t.querySum(1, r, r) t.update(1, r, k) // 倒水 return []int{r, c} } func (t *BookMyShow) Scatter(k, maxRow int) bool { // [0,maxRow] 的接水量之和 s : t.querySum(1, 0, maxRow) if s t.m*(maxRow1)-k { return false // 水桶已经装了太多的水 } // 从第一个没有装满的水桶开始 i : t.findFirst(1, maxRow, t.m-1) for k 0 { left : min(t.m-t.querySum(1, i, i), k) t.update(1, i, left) // 倒水 k - left i } return true }Go 版说明节点数组直接存{l, r, min, sum}四个字段省去递归传参l, r构建、更新、查询、二分的递归深度都依赖节点自身记录的区间边界sum在本题最大数据规模$n,m\le 10^9$ 量级下可能溢出int请按实际约束选择int64。JavaScriptclass BookMyShow { constructor(n, m) { this.n n; this.m m; const size 2 (32 - Math.clz32(n)); // 比 4n 更小 this.min Array(size).fill(0); this.sum Array(size).fill(0); } // 把下标 i 上的元素值增加 val update(o, l, r, i, val) { if (l r) { this.min[o] val; this.sum[o] val; return; } const m Math.floor((l r) / 2); if (i m) { this.update(o * 2, l, m, i, val); } else { this.update(o * 2 1, m 1, r, i, val); } this.min[o] Math.min(this.min[o * 2], this.min[o * 2 1]); this.sum[o] this.sum[o * 2] this.sum[o * 2 1]; } // 返回区间 [L,R] 内的元素和 querySum(o, l, r, L, R) { if (L l r R) { return this.sum[o]; } let res 0; const m Math.floor((l r) / 2); if (L m) { res this.querySum(o * 2, l, m, L, R); } if (R m) { res this.querySum(o * 2 1, m 1, r, L, R); } return res; } // 返回区间 [0,R] 中 val 的最靠左的位置不存在时返回 -1 findFirst(o, l, r, R, val) { if (this.min[o] val) { return -1; // 整个区间的元素值都大于 val } if (l r) { return l; } const m Math.floor((l r) / 2); if (this.min[o * 2] val) { return this.findFirst(o * 2, l, m, R, val); } if (R m) { return this.findFirst(o * 2 1, m 1, r, R, val); } return -1; } gather(k, maxRow) { // 找第一个能倒入 k 升水的水桶 const r this.findFirst(1, 0, this.n - 1, maxRow, this.m - k); if (r 0) { // 没有这样的水桶 return []; } const c this.querySum(1, 0, this.n - 1, r, r); this.update(1, 0, this.n - 1, r, k); // 倒水 return [r, c]; } scatter(k, maxRow) { // [0,maxRow] 的接水量之和 const s this.querySum(1, 0, this.n - 1, 0, maxRow); if (s this.m * (maxRow 1) - k) { return false; // 水桶已经装了太多的水 } // 从第一个没有装满的水桶开始 let i this.findFirst(1, 0, this.n - 1, maxRow, this.m - 1); while (k) { const left Math.min(this.m - this.querySum(1, 0, this.n - 1, i, i), k); this.update(1, 0, this.n - 1, i, left); // 倒水 k - left; i; } return true; } }Ruststruct BookMyShow { n: usize, m: i32, min: Veci32, sum: Veci64, } impl BookMyShow { // 把下标 i 上的元素值增加 val fn update(mut self, o: usize, l: usize, r: usize, i: usize, val: i32) { if l r { self.min[o] val; self.sum[o] val as i64; return; } let m (l r) / 2; if i m { self.update(o * 2, l, m, i, val); } else { self.update(o * 2 1, m 1, r, i, val); } self.min[o] self.min[o * 2].min(self.min[o * 2 1]); self.sum[o] self.sum[o * 2] self.sum[o * 2 1]; } // 返回区间 [L,R] 内的元素和 fn query_sum(self, o: usize, l: usize, r: usize, L: usize, R: usize) - i64 { if L l r R { return self.sum[o]; } let mut res 0; let m (l r) / 2; if L m { res self.query_sum(o * 2, l, m, L, R); } if R m { res self.query_sum(o * 2 1, m 1, r, L, R); } res } // 返回区间 [0,R] 中 val 的最靠左的位置不存在时返回 -1 fn find_first(self, o: usize, l: usize, r: usize, R: usize, val: i32) - i32 { if self.min[o] val { return -1; // 整个区间的元素值都大于 val } if l r { return l as i32; } let m (l r) / 2; if self.min[o * 2] val { return self.find_first(o * 2, l, m, R, val); } if R m { return self.find_first(o * 2 1, m 1, r, R, val); } -1 } fn new(n: i32, m: i32) - Self { let size 2 (32 - n.leading_zeros()) as usize; BookMyShow { n: n as usize, m, min: vec![0; size], sum: vec![0; size], } } fn gather(mut self, k: i32, max_row: i32) - Veci32 { // 找第一个能倒入 k 升水的水桶 let r self.find_first(1, 0, self.n - 1, max_row as usize, self.m - k); if r 0 { return vec![]; // 没有这样的水桶 } let c self.query_sum(1, 0, self.n - 1, r as usize, r as usize) as i32; self.update(1, 0, self.n - 1, r as usize, k); // 倒水 vec![r, c] } fn scatter(mut self, mut k: i32, max_row: i32) - bool { // [0,maxRow] 的接水量之和 let s self.query_sum(1, 0, self.n - 1, 0, max_row as usize); if s (self.m as i64 * (max_row 1) as i64) - k as i64 { return false; // 水桶已经装了太多的水 } // 从第一个没有装满的水桶开始 let mut i self.find_first(1, 0, self.n - 1, max_row as usize, self.m - 1) as usize; while k 0 { let left k.min(self.m - self.query_sum(1, 0, self.n - 1, i, i) as i32); self.update(1, 0, self.n - 1, i, left); // 倒水 k - left; i 1; } true } }说明原题解还提供 C 语言版本bookMyShowCreate/bookMyShowGather/bookMyShowScatter/bookMyShowFree四个导出函数配合malloc/calloc管理min与sum数组retSize指针回传返回数组长度与上述语言思路完全一致此处不再重复贴出读者可在原题解中查阅。六、仓库源码印证模板库中的线段树二分与自动化测试6.1 通用版 findFirst / findLast把二分抽象成回调本题的核心操作找第一个满足条件的位置在仓库模板库中被抽象成了通用方法见 copypasta/segment_tree.go// 线段树二分返回 [l,r] 内第一个满足 f 的下标如果不存在返回 -1 // 例如查询 [l,r] 内第一个大于等于 target 的元素下标需要线段树维护区间最大值 // t.findFirst(1, l, r, func(nodeMax int) bool { return nodeMax target }) func (t seg) findFirst(o, l, r int, f func(int) bool) int { if t[o].l r || t[o].r l || !f(t[o].val) { return -1 } if t[o].l t[o].r { return t[o].l } idx : t.findFirst(o1, l, r, f) if idx 0 { idx t.findFirst(o1|1, l, r, f) } return idx }该模板在注释里明确记录了线段树二分的两个应用LC2286即本题与LC2940Find Building Where Alice and Bob Can Meet对应提交记录submissions/517574644与517575667。由此可见本题正是模板库中线段树二分这一技巧的典型例题本题把f具体化为节点min值 $\le$ 阈值gather用m-kscatter用m-1从而在树内定向下探、找到最靠左可行桶。这与竞赛中常见的用线段树维护区间 max/min配合 findFirst 找第一个可行位置完全同构读者可对照库内findLast找最后一个满足条件的位置举一反三。6.2 仓库中的完整解法与自动化测试本题在仓库中的落地实现保存在 leetcode/biweekly/79/d/d.go与上文 Go 代码逐行一致含build、update、querySum、findFirst四个方法以及BookMyShow结构体。配套测试 leetcode/biweekly/79/d/d_test.go 由模板生成器生成通过 leetcode/testutil 的RunLeetCodeClassWithFile直接读取样例文件 leetcode/biweekly/79/d/d.txt 运行[BookMyShow,gather,gather,scatter,scatter] [[2,5],[4,0],[2,0],[5,1],[5,1]] [null, [0, 0], [], true, false] [BookMyShow,scatter] [[1,2],[2,0]] [null, true]第一组样例验证了完整语义gather(4,0)在 0 号桶倒入 4 升返回[0,0]gather(2,0)时 0 号桶只剩 1 升容量放不下 2 升返回[]scatter(5,1)可拆入 0 号1 升与 1 号4 升返回true而scatter(5,1)时两桶总接水量已达 9 升、剩余容量仅 1 升返回false——完整覆盖了可行/不可行、成组/分散四种分支。想本地复现的同学可在仓库根目录执行go test ./leetcode/biweekly/79/d/运行该测试模板生成方式见 copypasta/template/leetcode 的 generator。七、总结与迁移要点把本题学到的三件套记牢可复用于大量线段树二分题复合节点信息同一棵线段树可同时维护min与sum甚至更多聚合值只要信息在合并时能各自独立O(1)更新线段树二分利用节点聚合值min/max判断整段区间是否可能含答案从而在树内沿单一路径下探把二分查找与区间查询合并为一次 $\mathcal{O}(\log n)$ 的递归$2n$ 空间数组大小取2 ceil(log2(n))而非4n配合模板库中关于 $n$ 与节点编号关系的注释可精确预判空间占用、避免 Hack 数据越界。从水桶倒水这个等价场景出发本文完整还原了官方题解的算法推导、六语言实现与复杂度论证并以当前仓库的模板库源码和自动化测试作为佐证。掌握本文内容后你不仅会解 LC2286更能把区间内找第一个可行位置这一能力带到区间 mex、可持久化线段树二分等更进阶的题目中。赞分享科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载相关推荐力扣 2569 区间反转与求和查询用 Lazy 线段树维护 0-1 数组codeforces-go 仓库实战解析力扣 2569 区间反转与求和查询用 Lazy 线段树维护 0 1 数组codeforces go 仓库实战解析 导读 本篇以 leetcode/biwe科学计算力扣双周赛 186「最大合法数对和」枚举右维护左的 O(n) 解法与 codeforces-go 源码印证力扣双周赛 186「最大合法数对和」枚举右维护左的 O n 解法与 codeforces go 源码印证 本文以 codeforces go 仓库中 力扣双周科学计算力扣双周赛 172 全题解从二维 0-1 背包到 O(1) 位运算基于 codeforces-go 算法模板库力扣双周赛 172 全题解从二维 0 1 背包到 O 1 位运算基于 codeforces go 算法模板库 本篇技术指南以 leetcode/biwee科学计算上一篇Codex-X如何校验更新发布包发布校验脚本完整解读下一篇表情识别基于 OpenCV Keras 的七类人脸情绪识别实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑