线段树:这么高大上的名字以前从来不敢想23333.
线段树主要用于一段区间,其中包括 单点修改、区间修改、单点查询、区间查询,其中可能查询最大值或者一段区间的和。
它的本质把一段区间划分成一小段一小段。线段树的时间复杂度为O(logn)
我在想为什么单点是O(n),因为还是空间换时间了。数组不仅记录了单点的值,还记录了一个大段的值。
在查询的过程中,就不必一个一个点去查询,这样就会快。所以线段树也是一个不稳定的东西。
刚开始建树。一个一个输入叶子,在更新根的值。
1 void push_up(int rt) { 2 tree[rt] = tree[rt<<1] + tree[rt<<1|1]; 3 } 4 void build(int rt, int l, int r) { // rt节点编号 l是节点维护的区间的左端点 r右端点 5 if(l == r) { 6 scanf("%d", &a[l]); 7 tree[rt] = a[l]; 8 return ; 9 } 10 int m = l+r >> 1; 11 build(rt<<1, l, m); 12 build(rt<<1|1, m+1, r); 13 push_up(rt); 14 }
单点修改:
找到这个点的位置,单点增加。全部更新根的值
void update(int p, int x, int rt, int l, int r) { if(l == r) { tree[rt] += x; return ; } int m = l+r >> 1; if(p <= m) update(p, x, rt<<1, l, m); else update(p, x, rt<<1|1, m+1, r); push_up(rt); }
区间修改:
需要用一个lazy标记。
如果全包围,根的值要更新,lazy标记上。
如果不是全部包围,那么就要往下更新值,往下放。
往下放的意思就是 往下面小的区间去更新,把当前的lazy删除,加到下面两个区间的lazy,下面两个区间更新根的值。
最后再去小区间看一看。
一直递归到全包围为止。
1 void push_down(int rt, int len) { 2 lazy[rt<<1] += lazy[rt]; 3 lazy[rt<<1|1] += lazy[rt]; 4 tree[rt<<1] += (len - (len>>1)) * lazy[rt]; 5 tree[rt<<1|1] += (len>>1) * lazy[rt]; 6 lazy[rt] = 0; 7 } 8 void Update(int L, int R, int x, int rt, int l, int r) { 9 if(L <= l && r <= R) { 10 tree[rt] += (r-l+1)*x; 11 lazy[rt] += x; 12 return ; 13 } 14 if(lazy[rt]) push_down(rt, r-l+1); 15 int m = l+r >> 1; 16 if(L <= m) Update(L, R, x, rt<<1, l, m); 17 if(R >m) Update(L, R, x, rt<<1|1, m+1, r); 18 }
最后的最后查询啦
如果全包围,就直接输出。
不是全包围,有lazy就要往下放。
再往下找。
1 int query(int L, int R, int rt, int l, int r) { 2 if(L <= l && r <= R) return tree[rt]; 3 int m = l+r >> 1; 4 int ans = 0; 5 if(lazy[rt]) push_down(rt, r-l+1); 6 if(L <= m) ans += query(L, R, rt<<1, l, m); 7 if(R > m) ans += query(L, R, rt<<1|1, m+1, r); 8 return ans; 9 }