diff --git a/solution/0600-0699/0699.Falling Squares/README.md b/solution/0600-0699/0699.Falling Squares/README.md index b8fbd264d825b..717b00ceb94ea 100644 --- a/solution/0600-0699/0699.Falling Squares/README.md +++ b/solution/0600-0699/0699.Falling Squares/README.md @@ -72,12 +72,14 @@ tags: ### 方法一:线段树 -线段树将整个区间分割为多个不连续的子区间,子区间的数量不超过 $log(width)$。更新某个元素的值,只需要更新 $log(width)$ 个区间,并且这些区间都包含在一个包含该元素的大区间内。区间修改时,需要使用**懒标记**保证效率。 +根据题目描述,我们需要维护一个区间集合,支持区间的修改和查询操作。这种情况下,我们可以使用线段树来解决。 + +线段树将整个区间分割为多个不连续的子区间,子区间的数量不超过 $\log(width)$,其中 $width$ 是区间的长度。更新某个元素的值,只需要更新 $\log(width)$ 个区间,并且这些区间都包含在一个包含该元素的大区间内。区间修改时,需要使用**懒标记**保证效率。 - 线段树的每个节点代表一个区间; -- 线段树具有唯一的根节点,代表的区间是整个统计范围,如 $[1, N]$; +- 线段树具有唯一的根节点,代表的区间是整个统计范围,如 $[1, n]$; - 线段树的每个叶子节点代表一个长度为 1 的元区间 $[x, x]$; -- 对于每个内部节点 $[l, r]$,它的左儿子是 $[l, mid]$,右儿子是 $[mid + 1, r]$, 其中 $mid = ⌊(l + r) / 2⌋$ (即向下取整)。 +- 对于每个内部节点 $[l, r]$,它的左儿子是 $[l, mid]$,右儿子是 $[mid + 1, r]$, 其中 $\textit{mid} = \frac{l + r}{2}$; 对于本题,线段树节点维护的信息有: @@ -86,6 +88,8 @@ tags: 另外,由于数轴范围很大,达到 $10^8$,因此我们采用动态开点。 +时间复杂度方面,每次查询和修改的时间复杂度为 $O(\log n)$,总时间复杂度为 $O(n \log n)$。空间复杂度为 $O(n)$。 + #### Python3 @@ -388,7 +392,7 @@ func newNode(l, r int) *node { return &node{ l: l, r: r, - mid: int(uint(l+r) >> 1), + mid: (l + r) >> 1, } } @@ -474,6 +478,111 @@ func fallingSquares(positions [][]int) []int { } ``` +#### TypeScript + +```ts +class Node { + left: Node | null = null; + right: Node | null = null; + l: number; + r: number; + mid: number; + v: number = 0; + add: number = 0; + + constructor(l: number, r: number) { + this.l = l; + this.r = r; + this.mid = (l + r) >> 1; + } +} + +class SegmentTree { + private root: Node = new Node(1, 1e9); + + public modify(l: number, r: number, v: number): void { + this.modifyNode(l, r, v, this.root); + } + + private modifyNode(l: number, r: number, v: number, node: Node): void { + if (l > r) { + return; + } + if (node.l >= l && node.r <= r) { + node.v = v; + node.add = v; + return; + } + this.pushdown(node); + if (l <= node.mid) { + this.modifyNode(l, r, v, node.left!); + } + if (r > node.mid) { + this.modifyNode(l, r, v, node.right!); + } + this.pushup(node); + } + + public query(l: number, r: number): number { + return this.queryNode(l, r, this.root); + } + + private queryNode(l: number, r: number, node: Node): number { + if (l > r) { + return 0; + } + if (node.l >= l && node.r <= r) { + return node.v; + } + this.pushdown(node); + let v = 0; + if (l <= node.mid) { + v = Math.max(v, this.queryNode(l, r, node.left!)); + } + if (r > node.mid) { + v = Math.max(v, this.queryNode(l, r, node.right!)); + } + return v; + } + + private pushup(node: Node): void { + node.v = Math.max(node.left!.v, node.right!.v); + } + + private pushdown(node: Node): void { + if (node.left == null) { + node.left = new Node(node.l, node.mid); + } + if (node.right == null) { + node.right = new Node(node.mid + 1, node.r); + } + if (node.add != 0) { + let left = node.left, + right = node.right; + left!.add = node.add; + right!.add = node.add; + left!.v = node.add; + right!.v = node.add; + node.add = 0; + } + } +} + +function fallingSquares(positions: number[][]): number[] { + const ans: number[] = []; + const tree = new SegmentTree(); + let mx = 0; + for (const [l, w] of positions) { + const r = l + w - 1; + const h = tree.query(l, r) + w; + mx = Math.max(mx, h); + ans.push(mx); + tree.modify(l, r, h); + } + return ans; +} +``` + diff --git a/solution/0600-0699/0699.Falling Squares/README_EN.md b/solution/0600-0699/0699.Falling Squares/README_EN.md index a53d60a7a7b27..7f96d9c31475c 100644 --- a/solution/0600-0699/0699.Falling Squares/README_EN.md +++ b/solution/0600-0699/0699.Falling Squares/README_EN.md @@ -68,7 +68,25 @@ Note that square 2 only brushes the right side of square 1, which does not count -### Solution 1 +### Solution 1: Segment Tree + +According to the problem description, we need to maintain a set of intervals that support modification and query operations. In this case, we can use a segment tree to solve the problem. + +A segment tree divides the entire interval into multiple non-contiguous sub-intervals, with the number of sub-intervals not exceeding $\log(width)$, where $width$ is the length of the interval. To update the value of an element, we only need to update $\log(width)$ intervals, and these intervals are all contained within a larger interval that includes the element. When modifying intervals, we need to use **lazy propagation** to ensure efficiency. + +- Each node of the segment tree represents an interval; +- The segment tree has a unique root node representing the entire statistical range, such as $[1, n]$; +- Each leaf node of the segment tree represents a primitive interval of length 1, $[x, x]$; +- For each internal node $[l, r]$, its left child is $[l, \textit{mid}]$, and its right child is $[\textit{mid} + 1, r]$, where $\textit{mid} = \frac{l + r}{2}$; + +For this problem, the information maintained by the segment tree nodes includes: + +1. The maximum height $v$ of the blocks in the interval +2. Lazy propagation marker $add$ + +Additionally, since the range of the number line is very large, up to $10^8$, we use dynamic node creation. + +In terms of time complexity, each query and modification has a time complexity of $O(\log n)$, and the total time complexity is $O(n \log n)$. The space complexity is $O(n)$. @@ -372,7 +390,7 @@ func newNode(l, r int) *node { return &node{ l: l, r: r, - mid: int(uint(l+r) >> 1), + mid: (l + r) >> 1, } } @@ -458,6 +476,111 @@ func fallingSquares(positions [][]int) []int { } ``` +#### TypeScript + +```ts +class Node { + left: Node | null = null; + right: Node | null = null; + l: number; + r: number; + mid: number; + v: number = 0; + add: number = 0; + + constructor(l: number, r: number) { + this.l = l; + this.r = r; + this.mid = (l + r) >> 1; + } +} + +class SegmentTree { + private root: Node = new Node(1, 1e9); + + public modify(l: number, r: number, v: number): void { + this.modifyNode(l, r, v, this.root); + } + + private modifyNode(l: number, r: number, v: number, node: Node): void { + if (l > r) { + return; + } + if (node.l >= l && node.r <= r) { + node.v = v; + node.add = v; + return; + } + this.pushdown(node); + if (l <= node.mid) { + this.modifyNode(l, r, v, node.left!); + } + if (r > node.mid) { + this.modifyNode(l, r, v, node.right!); + } + this.pushup(node); + } + + public query(l: number, r: number): number { + return this.queryNode(l, r, this.root); + } + + private queryNode(l: number, r: number, node: Node): number { + if (l > r) { + return 0; + } + if (node.l >= l && node.r <= r) { + return node.v; + } + this.pushdown(node); + let v = 0; + if (l <= node.mid) { + v = Math.max(v, this.queryNode(l, r, node.left!)); + } + if (r > node.mid) { + v = Math.max(v, this.queryNode(l, r, node.right!)); + } + return v; + } + + private pushup(node: Node): void { + node.v = Math.max(node.left!.v, node.right!.v); + } + + private pushdown(node: Node): void { + if (node.left == null) { + node.left = new Node(node.l, node.mid); + } + if (node.right == null) { + node.right = new Node(node.mid + 1, node.r); + } + if (node.add != 0) { + let left = node.left, + right = node.right; + left!.add = node.add; + right!.add = node.add; + left!.v = node.add; + right!.v = node.add; + node.add = 0; + } + } +} + +function fallingSquares(positions: number[][]): number[] { + const ans: number[] = []; + const tree = new SegmentTree(); + let mx = 0; + for (const [l, w] of positions) { + const r = l + w - 1; + const h = tree.query(l, r) + w; + mx = Math.max(mx, h); + ans.push(mx); + tree.modify(l, r, h); + } + return ans; +} +``` + diff --git a/solution/0600-0699/0699.Falling Squares/Solution.go b/solution/0600-0699/0699.Falling Squares/Solution.go index 39d06c2c03902..7c5c40617bfa9 100644 --- a/solution/0600-0699/0699.Falling Squares/Solution.go +++ b/solution/0600-0699/0699.Falling Squares/Solution.go @@ -9,7 +9,7 @@ func newNode(l, r int) *node { return &node{ l: l, r: r, - mid: int(uint(l+r) >> 1), + mid: (l + r) >> 1, } } diff --git a/solution/0600-0699/0699.Falling Squares/Solution.ts b/solution/0600-0699/0699.Falling Squares/Solution.ts new file mode 100644 index 0000000000000..9b399cc966603 --- /dev/null +++ b/solution/0600-0699/0699.Falling Squares/Solution.ts @@ -0,0 +1,100 @@ +class Node { + left: Node | null = null; + right: Node | null = null; + l: number; + r: number; + mid: number; + v: number = 0; + add: number = 0; + + constructor(l: number, r: number) { + this.l = l; + this.r = r; + this.mid = (l + r) >> 1; + } +} + +class SegmentTree { + private root: Node = new Node(1, 1e9); + + public modify(l: number, r: number, v: number): void { + this.modifyNode(l, r, v, this.root); + } + + private modifyNode(l: number, r: number, v: number, node: Node): void { + if (l > r) { + return; + } + if (node.l >= l && node.r <= r) { + node.v = v; + node.add = v; + return; + } + this.pushdown(node); + if (l <= node.mid) { + this.modifyNode(l, r, v, node.left!); + } + if (r > node.mid) { + this.modifyNode(l, r, v, node.right!); + } + this.pushup(node); + } + + public query(l: number, r: number): number { + return this.queryNode(l, r, this.root); + } + + private queryNode(l: number, r: number, node: Node): number { + if (l > r) { + return 0; + } + if (node.l >= l && node.r <= r) { + return node.v; + } + this.pushdown(node); + let v = 0; + if (l <= node.mid) { + v = Math.max(v, this.queryNode(l, r, node.left!)); + } + if (r > node.mid) { + v = Math.max(v, this.queryNode(l, r, node.right!)); + } + return v; + } + + private pushup(node: Node): void { + node.v = Math.max(node.left!.v, node.right!.v); + } + + private pushdown(node: Node): void { + if (node.left == null) { + node.left = new Node(node.l, node.mid); + } + if (node.right == null) { + node.right = new Node(node.mid + 1, node.r); + } + if (node.add != 0) { + let left = node.left, + right = node.right; + left!.add = node.add; + right!.add = node.add; + left!.v = node.add; + right!.v = node.add; + node.add = 0; + } + } +} + +function fallingSquares(positions: number[][]): number[] { + const ans: number[] = []; + const tree = new SegmentTree(); + let mx = 0; + for (const [l, w] of positions) { + const r = l + w - 1; + const h = tree.query(l, r) + w; + mx = Math.max(mx, h); + ans.push(mx); + tree.modify(l, r, h); + } + return ans; +}