线段树 O(nlogn)
- 我们通常会遇到维护一个序列(区间)的问题,如区间修改,区间查询,单点修改,单点查询。
- A:我会暴力!
- B:我会分块!!
- 可是题目数据是个出题人就会卡暴力,只要1e5就轻松卡掉你。有人说他可以手动开O1,O2,O3,那都是弟弟们的做法。所以我们正式引入数据结构--线段树。
线段树
- 线段树是一棵二叉树,只是其中每个节点对应的是一段区间。
- 显然根节点对应的区间是[0,n-1],若一个点对应的是[l,r],则他的左右儿子应是[l,mid],[mid+1,r]这类似于一个二分的过程,叶子节点的l=r。我们也可以看出最后一层有n个节点[1,1]~[n,n],在往上则是n/2个节点以此类推线段树上一共2*n-1个节点。线段树的高是O(logn)级别的,当我们需要维护序列长度为2的整次幂时,线段树是一个满线段树,否则它的前h-1行时满线段树而最后一行可能不是。
普及完了概念性的东西我们步入正题
-
初始建树
- 线段树的每个节点都维护了所对应区间的最小值,我们可以用简单的递归完成这棵初始线段树。
- bulid(k,l,r)表示区间[l,r]的线段树k,若l==r则我们能直接构造一个叶子节点区间最小值是a1否则我们进行递归build(k*2,l,mid)和build(k*2+1,mid+1,r)。它的最小值就是两个儿子节点中的min值。这个过程是O(n)级别的,背下来不解释。还有数组要开到4n以防卡炸。
-
-
int t[N]; void build(int k,int l,int r) { if(l==r) { t[k]=v; return; } int min=(l+r)/2; build(2*k,l,mid); build(2*k+1,mid+1,r); t[k]=min(t[k*2],t[2*k+1]); } ......
建树过。。。qaq
-
-
单点修改
-
void update(int o,int l,int r,int ind,int ans) { if(l==r) { st[o]=ans; return; } int m=l+((r-l)>>1); if(ind<=m) { update(o<<1,l,m,ind,ans); } else{ update((o<<1)|1,m+1,r,ind,ans); } st[o]=max(st[o<<1],st[(o<<1)|1]); } ...... update(1,1,n,ind,ans);
更新一个值时我们需要把包含着个点的节点全部更新一次。我们可以直接修改如果递归到了叶子节点。否则更新左右子节点区间最小值较小的即可。
-
int t[N]; void updata(int k,int l,int r,int x,int r) { if(r<x||l>x)return; if(l==r&&l==x) { t[k]=v; return; } int mid=(l+r)/2; updata(k*2+1,mid+1,r,x,v); updata(k*2,l,mid,x,v); t[k]=min(t[k*2],t[k*2+1]); }
updataupdataupdata~qaq
-
-
区间查询