线段树
push up 由子节点算父节点的信息
push down操作 (懒标记/延迟标记)
将父节点的修改信息下传到子节点
1 2 3 4 5 6 7 8 9 10 11
| push up(int u)
build()
modify()
query()
|
定义:
线段树是一颗满二叉树;以一棵长度为10的序列为例:
![This is an test image](/2021/08/12/%E7%BA%BF%E6%AE%B5%E6%A0%91/1.png)
对于图中的段从上到下,从左到右依次编号为1,2,3 …..
因此某一段 u 的左儿子是 u<<1 ,右儿子是 u<<1|1 。
线段树的结构
1 2 3 4 5
| struct Node{ int l,r; int v; } tree[N*4];
|
线段树的建树:
1 2 3 4 5 6 7 8
| void build(int u,int l,int r){ tr[u]={l,r}; if(l==r) return ; int mid=l+r>>1; build(u<<1,l,mid),build(u<<1|1,mid+1,r); push_up(u); }
|
push_up操作
1 2 3 4
| void push_up(int u){ tree[u].v=max(tree[u<<1].v,tree[u<<1|1].v); }
|
查询操作
1 2 3 4 5 6 7 8 9 10
| int query(int u,int l,int r){ if(tree[u].l>=l&&tree[u].r<=r) return tree[u].v; int res=0; int mid=tree[u].l+tree[u].r >> 1; if(l<=mid) res=max(res,query(u<<1,l,r)); if(mid<r) res=max(res,query(u<<1|1,l,r)); return res; }
|
修改操作
1 2 3 4 5 6 7 8 9 10
| void modify(int u,int x,int v){ if(tree[u].l==x&&tree[u].r==x) tree[u].v=v; else{ int mid=tree[u].l+tree[u].r>>1; if(x<=mid) modify(u<<1,x,v); else modify(u<<1|1,x,v); push_up(u); } }
|
懒标记(延迟标记)push_down操作
- 为了支持区间的修改操作,线段树添加了延迟标记。
- 延迟标记,即在修改的时候对修改的区间上进行标记,
- 在查找或者下一次修改的时候,用父区间的懒标记更新当前区间的懒标记,并更新当前区间的信息。换而言之,在修改指令的时候遇到完整区间直接更新并回溯,但在回溯前增加标记。当后续指令需要递归此节点时,根据标记跟新此节点的两个儿子节点,并将标记传给儿子节点。
- 懒标记的意义表面此节点曾经被修改过,但是其儿子尚未被更新
添加懒标记后的更新的线段树函数
push_down通过父节点来更新子节点
1 2 3 4 5 6 7 8
| void push_down(int u){ Node &root=tree[u], &left=tree[u<<1], &right=tree[u<<1|1]; left.add+=root.add; left.sum=(left.r-left.l+1)*root.add; right.add+=root.add; right.sum=(right.r-right.l+1)*root.add; }
|
query查询函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| long long query(int u,int l,int r){ if(tree[u].l>=l&&tree[u].r<=r) return tree[u].sum; push_down(u); int mid=tree[u].l+tree[u].r>>1; if(r<=mid) return query(u<<1,l,r); else if(l>mid) return query(u<<1|1,l,r); else{ long long suml,sumr; if(l<=mid) suml+=query(u<<1,l,r); if(r>mid) sum2+=query(u<<1|1,l,r); return suml+sumr; } }
|
modify修改函数
1 2 3 4 5 6 7 8 9 10 11 12
| void modify(int u,int l,int r,int d){ if(tree[u].l>=l&&tree[u].r<=r) { tree[u].sum+=(tree[u].r-tree[u].l+1)*d; tree[u].add+=d; return ; } push_down(u); int mid=tree[u].l+tree[u].r>>1; if(l<=mid) modify(u<<1,l,r,d); if(r>mid) modify(u<<1|1,l,r,d); push_up(u); }
|
当有多个需要维护的懒标记时,即有多个懒标记的时候:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void cal(Node &root,int add,int mul){ root.sum=root.sum*mul+add*(root.r-root,l+1); root.mul=root.mul*mul; root.add=root.add*mul+add; }
void push_up(int u){ tree[u].sum=tree[u<<1].sum+tr[u<<1|1].sum; tree[u].add=0,tree[u].mul=1; }
void push_down(int u){ cal(tree[u<<1],tree[u].add,tree[u].mul); cal(tree[u<<1|1],tree[u].add,tree[u].mul); root.add=0,root.mul=1; }
|
扫描线
在有重复覆盖的长方形面积题型中,使用线段树维护区间扫描线:
长方形左侧为+1线,右侧为-1线,使用线段树可动态维护,修改一段区间或者获取一段区间内区间的长度
值得注意的是边界问题
1 2 3 4 5
| Node{ int l,r; int cnt; double len; }
|