2022-09-12更新,更好修改的版本
class SegmentTree {
// 堆
public arr: number[]
constructor(n: number = 0, fill: number = 0) {
this.arr = new Array(n * 4)
this.arr.fill(fill)
}
/**
* 构建线段树
* @param list 原始数组
* @param idx 线段树堆索引 (一般取1)
* @param l 当前区间起点 (一般取0)
* @param r 当前区间终点 (一般取list.length - 1)
*/
build(list: number[], idx: number, l: number, r: number) {
if (l === r) {
this.arr[idx] = list[l]
return
}
let mid = (l + r) >> 1
// 构建左子树
this.build(list, idx << 1, l, mid)
// 构建右子树
this.build(list, (idx << 1) + 1, mid + 1, r)
// 更新节点值
this.arr[idx] = this.arr[idx << 1] + this.arr[(idx << 1) + 1]
}
/**
* 区间查询
* @param idx 线段树堆索引 (一般取1)
* @param l 当前区间起点 (一般取0)
* @param r 当前区间终点 (一般取list.length - 1)
* @param L 要查询的区间的起点
* @param R 要查询的区间的终点
*/
queryRange(idx: number, l: number, r: number, L: number, R: number): number {
// 当前区间与待查区间不相交
if (r < L || l > R) return 0
// 当前区间完全被待查区间覆盖
if (l === L && r === R) return this.arr[idx]
let mid: number = (l + r) >> 1
// 查询区间取左区间与原查询区间的交集
let resL: number = this.queryRange(idx << 1, l, mid, Math.max(l, L), Math.min(mid, R))
// 查询区间取右区间与原查询区间的交集
let resR: number = this.queryRange((idx << 1) + 1, mid + 1, r, Math.max(mid + 1, L), Math.min(r, R))
return resL + resR
}
/**
* 区间查询
* @param idx 线段树堆索引 (一般取1)
* @param val 更新后的值
* @param l 当前区间起点 (一般取0)
* @param r 当前区间终点 (一般取list.length - 1)
* @param L 要查询的区间的起点
* @param R 要查询的区间的终点
*/
updateRange(idx: number, val: number, l: number, r: number, L: number, R: number) {
// 当前区间与待查区间不相交
if (r < L || l > R) return this.arr[idx]
// 当前区间长度为1,更新区间
if (l === r) return this.arr[idx] = val
let mid: number = (l + r) >> 1
// 查询区间取左区间与原查询区间的交集
let resL: number = this.updateRange(idx << 1, val, l, mid, Math.max(l, L), Math.min(mid, R))
// 查询区间取右区间与原查询区间的交集
let resR: number = this.updateRange((idx << 1) + 1, val, mid + 1, r, Math.max(mid + 1, L), Math.min(r, R))
this.arr[idx] = resL + resR
return this.arr[idx]
}
}
/* 测试样例
~function test() {
let list: number[] = [3, 2, 1, 4, 5]
let len = list.length - 1
let st: SegmentTree = new SegmentTree(list.length)
st.build(list, 1, 0, list.length - 1)
// 测试查询
console.log(st.queryRange(1, 0, len, 1, 3)) // 7, 2+1+4
// 测试更新
st.updateRange(1, 4, 0, len, 1, 1) // 将区间[1,1]更新为4, [3, 4, 1, 4, 5]
console.log(st.queryRange(1, 0, len, 0, 3)) // 12, 3+4+1+4
st.updateRange(1, 4, 0, len, 0, 3) // 将区间[0,3]更新为4, [4, 4, 4, 4, 5]
console.log(st.queryRange(1, 0, len, 0, 3)) // 16, 4+4+4+4
}()
*/
含有懒惰标记
class SegmentTreeNode {
// 该节点的区间起点
public start: number
// 该节点的区间终点
public end: number
// 该节点的区间结果
public res: number
// 懒惰标记
public lazy: number
constructor(start: number, end: number) {
this.start = start
this.end = end
this.lazy = 0
}
}
class SegmentTree {
// 堆
public arr: SegmentTreeNode[]
constructor(list?: number[]) {
this.arr = []
if (!list) return this
this.build(list, 0, list.length - 1)
}
/**
* 构建线段树 (分割区间【start,mid】、【mid+1,end】)
* @param list base数组
* @param start 区间起点
* @param end 区间终点
* @param arrIndex 线段树堆指针,默认1
*/
build(list: number[], start: number, end: number, arrIndex: number = 1): void {
let node: SegmentTreeNode = new SegmentTreeNode(start, end)
if (start === end) {
node.res = list[start]
this.arr[arrIndex] = node
return
}
let mid = (start + end) >> 1
// 构建左子树
this.build(list, start, mid, arrIndex << 1)
// 构建右子树
this.build(list, mid + 1, end, (arrIndex << 1) + 1)
// 更新根节点值
node.res = this.arr[arrIndex << 1].res + this.arr[(arrIndex << 1) + 1].res
this.arr[arrIndex] = node
}
// 下推懒惰节点
lazyDown(root): void {
let leftNode: SegmentTreeNode = this.arr[root << 1],
rightNode: SegmentTreeNode = this.arr[(root << 1) + 1],
node: SegmentTreeNode = this.arr[root]
if (leftNode) {
leftNode.lazy += node.lazy
leftNode.res += node.lazy * (leftNode.end - leftNode.start + 1)
}
if (rightNode) {
rightNode.lazy += node.lazy
rightNode.res += node.lazy * (rightNode.end - rightNode.start + 1)
}
node.lazy = 0
}
// 修改区间结果
change(start: number, end: number, val: number, root: number = 1): void {
let node = this.arr[root]
if (!node) return
// 当前区间被结果区间完全覆盖,执行更新
if (node.start >= start && node.end <= end) {
// 区间内所有的数字都+val
node.res += val * (node.end - node.start + 1)
node.lazy += val
return
}
// 当前区间完全覆盖结果区间,更新当前区间结果
if (start >= node.start && end <= node.end) {
// 区间内在 【start, end】 范围内的数字+val
node.res += val * (end - start + 1)
}
// 下推懒惰节点
this.lazyDown(root)
// 此时区间未完全覆盖,需要继续搜索左右子树
if (node.start <= end) // 与左区间有交集
this.change(start, end, val, root << 1)
if (start <= node.end) // 与右区间有交集
this.change(start, end, val, (root << 1) + 1)
}
// 获取区间结果
getRangeRes(start: number, end: number = start, root: number = 1): number {
let node = this.arr[root]
// 该段区间无值,返回0
if (!node) return 0
// 当前区间被完全覆盖,直接返回结果即可
if (node.start >= start && node.end <= end) {
return node.res
}
// 下推懒惰节点
this.lazyDown(root)
let resL = 0, resR = 0
// 此时区间未完全覆盖,需要继续搜索左右子树
if (node.start <= end) // 与左区间有交集
resL = this.getRangeRes(start, end, root << 1)
if (start <= node.end) // 与右区间有交集
resR = this.getRangeRes(start, end, (root << 1) + 1)
return resL + resR
}
}
/* 测试样例
~function test() {
let st: SegmentTree = new SegmentTree([3, 2, 1, 4, 5])
// 测试查询
console.log(st.getRangeRes(1, 4)) // 12, [2,1,4,5]
console.log(st.getRangeRes(3, 4)) // 9, [4,5]
console.log(st.getRangeRes(3))// 4, [4]
console.log(st.getRangeRes(0, 5))// 15, [3,2,1,4,5]
console.log('---')
// 测试修改
st.change(0, 4, 2)// 区间【0,4】的数都+2
console.log(st.getRangeRes(0)) // 5, [3+2]
console.log(st.getRangeRes(2, 4))// 16, [1+2,4+2,5+2]
console.log(st.getRangeRes(1, 3))// 13, [2+2,1+2,4+2]
console.log(st.getRangeRes(9,10))// 0, []
}()
*/
不含懒惰标记
class SegmentTreeNode {
// 该节点的区间起点
public start: number
// 该节点的区间终点
public end: number
// 该节点的区间结果
public res: number
constructor(start: number, end: number) {
this.start = start
this.end = end
}
}
class SegmentTree {
// 堆
public arr: SegmentTreeNode[]
constructor(list?: number[]) {
this.arr = []
if (!list) return this
this.build(list, 0, list.length - 1)
}
/**
* 构建线段树 (分割区间【start,mid】、【mid+1,end】)
* @param list base数组
* @param start 区间起点
* @param end 区间终点
* @param arrIndex 线段树堆指针,默认1
*/
build(list: number[], start: number, end: number, arrIndex: number = 1): void {
let node: SegmentTreeNode = new SegmentTreeNode(start, end)
if (start === end) {
node.res = list[start]
this.arr[arrIndex] = node
return
}
let mid = (start + end) >> 1
// 构建左子树
this.build(list, start, mid, arrIndex << 1)
// 构建右子树
this.build(list, mid + 1, end, (arrIndex << 1) + 1)
// 更新根节点值
node.res = this.arr[arrIndex << 1].res + this.arr[(arrIndex << 1) + 1].res
this.arr[arrIndex] = node
}
// 修改区间结果
change(start: number, end: number, val: number, root: number = 1): void {
let node = this.arr[root]
if (!node) return
// 当前区间被结果区间完全覆盖,执行更新
if (node.start >= start && node.end <= end) {
// 区间内所有的数字都+val
node.res += val * (node.end - node.start + 1)
}
// 当前区间完全覆盖结果区间,更新当前区间结果
else if (start >= node.start && end <= node.end) {
// 区间内在 【start, end】 范围内的数字+val
node.res += val * (end - start + 1)
}
// 此时区间未完全覆盖,需要继续搜索左右子树
if (node.start <= end) // 与左区间有交集
this.change(start, end, val, root << 1)
if (start <= node.end) // 与右区间有交集
this.change(start, end, val, (root << 1) + 1)
}
// 获取区间结果
getRangeRes(start: number, end: number = start, root: number = 1): number {
let node = this.arr[root]
// 该段区间无值,返回0
if (!node) return 0
// 当前区间被完全覆盖,直接返回结果即可
if (node.start >= start && node.end <= end) {
return node.res
}
let resL = 0, resR = 0
// 此时区间未完全覆盖,需要继续搜索左右子树
if (node.start <= end) // 与左区间有交集
resL = this.getRangeRes(start, end, root << 1)
if (start <= node.end) // 与右区间有交集
resR = this.getRangeRes(start, end, (root << 1) + 1)
return resL + resR
}
}
/* 测试样例
~function test() {
let st: SegmentTree = new SegmentTree([3, 2, 1, 4, 5])
// 测试查询
console.log(st.getRangeRes(1, 4)) // 12, [2,1,4,5]
console.log(st.getRangeRes(3, 4)) // 9, [4,5]
console.log(st.getRangeRes(3))// 4, [4]
console.log(st.getRangeRes(0, 5))// 15, [3,2,1,4,5]
console.log('---')
// 测试修改
st.change(0, 4, 2)// 区间【0,4】的数都+2
console.log(st.getRangeRes(0)) // 5, [3+2]
console.log(st.getRangeRes(2, 4))// 16, [1+2,4+2,5+2]
console.log(st.getRangeRes(1, 3))// 13, [2+2,1+2,4+2]
console.log(st.getRangeRes(9,10))// 0, []
}()
*/