参考:线段树介绍
静态线段树
假设大小为n,则至少需要申请大小为4*n的数组,一般用在左右边界不大的情况,对应力扣:区间和的个数,代码模板:
class SegTree{
int n;
int[] arr;
SegTree(int n){
this.n = n;
this.arr = new int[4*n];
}
public void update(int x){
update(1,1,n,x);
}
public int query(int l, int r){
return query(1,1,n,l,r);
}
private void update(int id, int l, int r, int val){
if(val<l || r<val) return;
arr[id]++;
if(l==r) return;
int mid = l+(r-l)/2;
update(id<<1, l, mid, val);
update((id<<1)+1, mid+1, r, val);
}
private int query(int id, int l, int r, int tl, int tr){
if(tr<l || r<tl) return 0;
else if(tl<=l && r<=tr) return arr[id];
int mid = l+(r-l)/2;
return query(id<<1, l, mid, tl, tr) + query((id<<1)+1, mid+1, r, tl, tr);
}
}
动态线段树
就是建立一颗二叉树,每个节点包括左区间(left)和右区间(right),一般用在左右边界很大并且数据之间间隔也很大的情况(比如-inf,0,1000,inf),对这些数据进行排序并用哈希map离散化后,可以将左右边界缩小,便于存入树中,该模板内部封装一个建树的静态方法,对应力扣通过指令创建有序数组,考虑到该题目数值可能会很大,所以采用动态构建,当然静态也可以除非你的内存很刚,对应模板:
java模板:
class SegNode{
int left,right;
int sum = 0;
SegNode left_child, right_child;
SegNode(int left, int right){
this.left = left;
this.right = right;
}
public void update(int val){
if(val<left || right<val) return;
sum++;
if(left==right) return;
left_child.update(val);
right_child.update(val);
}
public int query(int l, int r){
if(r<left || right<l) return 0;
else if(l<=left && right<=r) return sum;
return left_child.query(l,r)+right_child.query(l,r);
}
public static void print(SegNode node){
if(node==null) return;
System.out.println(node.left+" "+node.right+" "+node.sum);
print(node.left_child);
print(node.right_child);
}
public static SegNode build(int min, int max){
SegNode root = new SegNode(min,max);
if(min==max) return root;
int mid = min+(max-min)/2;
root.left_child = build(min, mid);
root.right_child = build(mid+1, max);
return root;
}
}
C++模板(更新时间20220113),对上面的java模板进行优化,加入懒开辟,即不在在使用SetTree之前就把MIN~MAX范围的所有元素准备好,而是动态运行时开辟,直接上模板:
class SegNode {
public:
int left_edge, right_edge;
int sum = 0;
SegNode *left_child, *right_child;
SegNode(int left_edge, int right_edge) : left_edge(left_edge), right_edge(right_edge), left_child(nullptr),
right_child(nullptr) {}
void update(int val, int num) {
if (val < left_edge || right_edge < val) return;
sum += num;
if (left_edge == right_edge) return;
int mid = left_edge + (right_edge - left_edge) / 2;
// 动态运行时开辟
if (left_child == nullptr) left_child = new SegNode(left_edge, mid);
if (right_child == nullptr) right_child = new SegNode(mid + 1, right_edge);
left_child->update(val, num);
right_child->update(val, num);
}
int query(int l, int r) {
if (r < left_edge || right_edge < l) return 0;
if (l <= left_edge && right_edge <= r) return sum;
// 如果左右孩子为空,则说明之前木有遍历过这里,直接返回0
return (left_child != nullptr ? left_child->query(l, r) : 0) +
(right_child != nullptr ? right_child->query(l, r) : 0);
}
};
区间查找,区间查询(线段树),采用懒查询的方式,提高时间复杂度
例题:LCP 05
class SegTree:# 区间增加,区间查询
def __init__(self, N: int):
self.N = N
self.value = [0] * 4 * N
self.lazy = [0] * 4 * N
self.MOD = 1e9+7
def __push_down(self, id:int, left:int, right:int) -> None: #用于懒查询,因为不一定将来会查询到这里,这么做可以减少时间复杂度
if self.lazy[id] == 0: return
self.value[id] += self.lazy[id]*(right-left+1)
if left < right:
self.lazy[id << 1] += self.lazy[id]
self.lazy[(id << 1) | 1] += self.lazy[id]
self.lazy[id] = 0
def update(self, left: int, right: int, value: int) -> None:
self.__update(1, 1, self.N, left, right, value)
def __update(self, id:int, opleft:int, opright:int, left:int, right:int, value:int) -> None:
self.__push_down(id, opleft, opright)
if right < opleft or opright < left: return
if left <= opleft and opright <= right:
self.lazy[id] = value
self.__push_down(id, opleft, opright)
return
mid = (opleft+opright)>>1
self.__update(id<<1, opleft, mid, left, right, value)
self.__update((id<<1)|1, mid+1, opright, left, right, value)
self.value[id] = int((self.value[id<<1] + self.value[(id<<1)|1]) % self.MOD)
def quire(self, left: int, right: int) -> int:
return self.__quire(1, 1, self.N, left, right)
def __quire(self, id: int, opleft: int, opright: int, left: int, right: int) -> int:
if right < opleft or opright < left: return 0
self.__push_down(id, opleft, opright)
if left <= opleft and opright <= right: return self.value[id]
mid = (opleft + opright) >> 1
return int((self.__quire(id << 1, opleft, mid, left, right) + \
self.__quire((id << 1) | 1, mid + 1, opright, left, right)) % self.MOD)
树状数组
模板(C++版):
class BinaryIndexTree{
public:
vector<T> arr;
BinaryIndexTree(int n){
arr = vector<T>(n, 0);
}
int lowbit(int n){
return n&(-n);
}
void update(int x, T num){
for(int i=x; i<SIZE; i+=lowbit(i)){
arr[i] += num;
}
}
T get(int x){
T ans = 0;
for(int i=x; i>0; i-=lowbit(i)){
ans += arr[i];
}
return ans;
}
};
例题:数字流的秩
#define N 500001
class StreamRank {
public:
// 可以计算从0~50000之间
int arr[N+1];
StreamRank() {
memset(arr, 0, sizeof(arr));
}
int lowbit(int x){
return x&(-x);
}
void add(int x, int num){
for(int i=x; i<=N; i+=lowbit(i)) arr[i] += num;
}
int query(int x){
int ans = 0;
for(int i=x; i>0; i-=lowbit(i)) ans += arr[i];
return ans;
}
// 考虑到不能够为0,所以从x+1开始算
void track(int x) {
add(x+1, 1);
}
int getRankOfNumber(int x) {
return query(x+1);
}
};