目的:线段树可以理解为解决庞大数据的一个工具,接近于数据结构。。将很多场景下O(n)的复杂度降至O(logn) 本篇会介绍些关于线段树单点更新-区间求和、区间更新-区间求和(本部分可能稍微难理解。。涉及懒标记、向下更新等%%)。当然也可稍加改动变为切合题意的求最大值或最小值等等哦~
首先来看一下区间的概念,运用二分将其一层层分至l==r,有因为是二叉树结构,试想它的区间结构划分是什么样的。。。如下图给出更直观的形式:
- 单点更新-区间求和
顾名思义。。所要进行的操作 第一 针对单个数据增减;第二 针对一个连续区间求和。进行单点更新就引起了对整个线段树的维护。。怎么维护?上篇提到每个节点的值都是它所分出来的两个子节点的和,所以我们在改变叶子节点的值的时候要同时更新与它有关的节点(父节点)的信息。
寻找子节点的方法:
- 如果这个区间的中点在所求节点的 右边 ,那么就往左找
- 如果这个区间的中点在所求节点的 左边 ,那么就往右找
特别注意:由于左子节点是 [l, m] ,右子节点是 [m + 1, r] ,因此条件向左应该是 m >= p
在此把向上更新节点封装成一个函数 push_up(int t)
加入“单点更新”及“向上更新函数”代码:
//继承上节的定义 #define MAXN int(5e4 + 9) #define DEFI int l, int r, int t #define INIF 1, n, 1 #define LSON l, (l + r) >> 1, t << 1 #define RSON ((l + r) >> 1) + 1, r, t << 1|1 int S[MAXN], T[MAXN << 2]; //向上更新 - 求和树就是将这一节点的值赋为这个节点的两个子节点的值的和 void push_up(int t) { T[t] = T[t << 1] + T[t << 1|1]; //其实取最大值最小值只需要在这里换一下 push_up() 函数即可 //T[t] = max(T[t << 1], T[t << 1|1]); //T[t] = min(T[t << 1], T[t << 1|1]); return ; } // p 是第几个数,n 是改变的值 void update(int p, int n, DEFI) { //如果遇到叶子节点,则为我们要更新的节点 if (l == r) { T[t] += n; return ; } //如果不是叶子节点,则向子节点递归 int m = (l + r) >> 1; if (m >= p) update(p, n, LSON); else update(p, n, RSON); //更新过子节点后应该更新一下当前节点 push_up(t); return ; }
可以更新一个原数据的值后。。那就来思考下怎么进行区间求和 ,//求所在区间的所有叶子结点值的和,,在层层搜索时理解起来稍微有些难度~
加入区间求和代码:
//返回一个整数,参数需要所求区间的左边界(L)和右边界(R) int query(int L, int R, DEFI) { if (L <= l && R >= r) return T[t];//搜索区间被完全包含在目标区间内,直接返回该区间的值 int m = (l + r) >> 1, res = 0; //如果这个区间的左子区间和目标区间有交集,那么搜索左子区间 if (L <= m) res += query(L, R, LSON); //如果这个区间的右子区间和目标区间有交集,那么搜索右子区间 if (R > m) res += query(L, R, RSON); return res; }
补充:由于右子区间是 [m + 1, r] ,因此判断条件是
R > m
- 区间更新-区间求和
一些题目要求对区间内所有的元素都加上一个数或减去一个数 //有可能有一部分人会想根据已会的知识,算出结果然后减去或加上所要改变的值,其实不然,那接下来的操作怎么办。。注意:线段树是对树的维护。
解决区间更新时,根据已会的知识,会想到把每个需要更换的叶子节点都进行一次单点更新操作,然后进行区间求和....en 这样想也没有错 考虑到复杂度问题~ 对于大量要更新的数据还是要想个办法优化一下h。。
重温下线段树之所以优化于线性数组,,是因为它能将 一段数据 当成 一个数据 来直接使用。在此引入一个新的概念:懒标记 。
懒标记的意义就是能跟新大区间先更新大区间,不更新下面的子节点,但是另开一个标记树标记一下,等之后用到下面的节点的时候如果标记树里有东西,再更新下面的节点,这样子就能做到依然是 “用到哪里走到哪里” 。。看起来还是有点难哈哈~ 说了这么多了 看看代码怎么写。。
区间更新-区间求和代码:
//一个节点对应一个标记,所以大小和树是一样的 int V[SMAX << 2]; //顺便一提有的人也会把树叫 tree[] ,懒标记叫 lazy[] //数组叫什么是个人习惯哈,我喜欢用一个字母所以就这样了 void push_down(int t, int s) { if (V[t]) { V[t << 1] += V[t]; V[t << 1|1] += V[t]; T[t << 1] += V[t << 1]*(s - (s >> 1)); T[t << 1|1] += V[t << 1|1]*(s >> 1); V[t] = 0; } return ; } void update(int L, int R, int n, DEFI) { //这个区间被完全包括在目标区间里面,直接更新管理这个区间的节点 if (L <= l && R >= r) { T[t] += n*(r - l + 1); V[t] += n; return ; } push_down(t, r - l + 1); int m = (l + r) >> 1; //区间的左子区间和目标区间有交集,那么向左子区间更新 if (L <= m) update(L, R, n, LSON); //区间的右子区间和目标区间有交集,那么向右子区间更新 if (R > m) update(L, R, n, RSON); //如果要向子区间更新,递归之前应先将懒标记传递下去 push_up(t); return ; } int query(int L, int R, DEFI) { if (L <= l && R >= r) return T[t]; //查询子节点之前应先判断一下当前节点是否有没有更新的懒标记存在 push_down(t, r - l + 1); int m = (l + r) >> 1, res = 0; if (L <= m) res += query(L, R, LSON); if (R > m) res += query(L, R, RSON); return res; }
补充:同样的由于右子区间是 [m + 1, r] ,因此判断条件是
R > m
向上更新父节点(调用 push_up)
向下传递懒标记(调用 push_down)
总结完这篇就可写些线段树的基础题辣!!
附上题目链接嘿嘿 。。
http://acm.hdu.edu.cn/showproblem.php?pid=1698
http://acm.hdu.edu.cn/showproblem.php?pid=1166