线段树 专题
一.关于线段树
0.说在前面
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。
1.例题
2.认识线段树
线段树,顾名思义,就是将一个大的区间划分成一个一个小的区间,直到剩余自己为止,然后形成一棵树,如下图,这是一棵1到10的线段树:
3.建立线段树
有一个一维数组 a[6] = { 0 , 1 , 2, 3 , 4 , 5 , 6 },把他建立成一棵线段树可以这样做:
设线段树的根节点为1,用数组 tree 来储存我们的线段树, tree[i] 表示在 i 节点上的值(区间最小值 or 区间最大值 or 区间总和),这是一棵最大值的线段树。
黑色圆框里的是区间,tree[i] 是节点,红色的数是 tree[i] 的值。
不难发现 tree[1] = max ( a[1] , a[2] , a[3] , a[4] , a[5] , a[6] )。
我们通过观察,可以发现当前节点 i 的左节点为 2i ,右节点为 2i+1 。我们继续观察,有没有发现如果 tree[i] 表示的区间大小等于 1 的话(区间大小指的是区间包含的元素的个数,即 a 的个数。设 tree[j] 表示区间 [s,t],它的区间大小就是 t-s+1),那么 tree[i] 所表示的区间 [s,t] 中肯定有 s=t,且 tree[i] = a[s] = a[t] 。这就是线段树的递归边界。
让我们一起看一下如何建树的程序吧,code:
void build(int i,int x,int y)
{
if(x==y)
{
tree[i]=a[x];
return;
}
int m=(x+y)/2;
build(i+i,x,m);
build(i+i+1,m+1,y);
tree[i]=max(tree[i+i],tree[i+i+1]);
}
4.查询线段树
查询线段树的 [l,r] 的区间的最大值 or 最小值 or 总和 , 我们假设要枚举 [1,6] ,就直接选择 tree[1] 的值,因为 tree[1] 的区间就是为 [1,6],如图:
如果我们要查询 [2,5] 区间内的最大值,可以先分成 [2,3] [4,5] ,最后在把这两个区间分成更小的区间 [2,2] [3,3] [4,4] [5,5] ,如下图:
分好区间后,可以先查询 [2,2] [3,3] [4,4] [5,5] 分别的值,查到后返回到 [2,3] [4,5] 中,最后就查询到了 [2,5] 区间的最大值了。不懂的可以琢磨一下代码:
void qeury(int i,int x,int y)
{
if(y<pl||x>pr)
return;
if(x>=pl&&y<=pr)
{
px=max(px,tree[i]);
return;
}
int m=(x+y)/2;
down(i);
qeury(i+i,x,m);
qeury(i+i+1,m+1,y);
}
5.线段树区间修改
(1)朴素算法
像查询线段树差不多,找到了就修改,现在要重点将 懒惰标志 。
(2)懒惰标志
我们先看个故事:
一个父亲,他的名字叫 father ,他有两个儿子,分别为 the first son,the second son 。一天,父亲收到了 somebody 给他两个儿子的两个红包,每一个红包里有 100 元,这个父亲想:我的两个儿子住的离我这么远,送过去的话还要花费我很多的车费,不值,先存着,等下一次收红包时一起给。(不要问为什么不网上转钱)
接着,他就给他两儿子发消息说:“儿啊,我收到了有人给你们的红包,跑去你们那太费劲,先存着”
他两儿子开到就怒了,说:“为什么?那是我们的红包!!!”
父亲说:“也不是不给,等到下次有红包在一起给,我先记着,我X年X月X日 … … 欠 … … 红包,好了,我欠条都写好了,不赖账吧。”
文中父亲懒得去把红包送去给儿子,就用了 lazy tag 先存着一个标志,等到下次询问才往下传,少花费了时间。
让我们康康如何写代码:
void jia(int i, int v) {
t[i] += v, lz[i] += v;
}
void down(int i) {
if(lz[i]) {
jia(i + i, lz[i]);
jia(i + i + 1, lz[i]);
lz[i] = 0;
}
}
void add(int i, int x, int y) {
if(y < pl || x > pr) return;
if(x >= pl && y <= pr) {
jia(i, px);
return;
}
int m = (x + y) / 2; down(i);
add(i + i, x, m);
add(i + i + 1, m + 1, y);
t[i] = max(t[i + i], t[i + i + 1]);
}
二.后记
今天的线段树就到这里。