一直没碰过线段树,个人认为好长好难,不过这几天做题遇到了裸的线段树的题,TAT。

线段树我理解就是把二叉树的左右节点现在分别看成是两个区间。

那么现在这两个区间的端点怎么存放?怎么能够把这个区间里的数(一般指的就是在这个区间的数值的和)存放起来呢?

比较传统化的是用一个结构体来存放。比如:

struct node
{
int l, r; // l,r分别是左右端点
int w; // 用来存放这个区间的值(和)
};

这样就可以解决了数值的存放问题了。

学习建立二叉树的时候是用指针、结构体来建立的,依靠指针来找子节点或者根节点,当然在线段树中依然可以那么建立,不过

在使用时可能会因为指针的特点,RE之类的错误经常出现,于是就是就有人想到用结构体类型数组来模拟建立。


这个结构体需要开多么大才合适呢?

一般需要开4倍空间才可以。(当时自我感觉认为3倍就够了,但是RE了一次,可以在纸上手动画一下,帮助理解)


线段树一般就是来解决比较直观的问题(当然也有好多神级题目来考你的线段树,这里暂时忽略一下),比如给你一个N长度的一

组数,再给你L、R。这样就可以问你:任意L~R之间的数的和(这就是所谓区间查询,当L==R时,就变成了单点查询)。

如果仅仅是查询那么前缀和就可以做了,如果数据N不大,直接暴力就好了。

现在再加一个操作,给你L、R、X,每次在L~R区间上加上X(这就是区间修改,当L==R时,就是单点修改),这个操作后再查

询,这两个操作的次数非常多,一般都会TLE了。

这样的问题就可以用线段树来解决了。


首先是建一颗线段树:

void BuildSegmentTree(int k, int l, int r)
{
tree[k].l = l; // 存放左右端点
tree[k].r = r;
if(l == r) // 如果是l==r时,就是到了线段树的最下层,也就是叶子,存放数本身的值。
{
scanf("%lld", &tree[k].w);
return ; // !!!
}
int m = (tree[k].l + tree[k].r) >> 1; // m = (tree[k].l + tree[k].r) / 2;
BuildSegmentTree(k << 1, l, m);
BuildSegmentTree(k << 1 | 1, m + 1, r);
tree[k].w = tree[k << 1].w + tree[k << 1 | 1].w; // tree[k << 1 | 1].w 相当于 tree[k * 2 + 1].w
}

建树的过程比较简单,就是递归建树就可以了。

<以下操作的英文名字纯属为了避免混淆了,根据自己喜欢命名即可>


1、单点修改(Single point modification)

void Single_Point_Modification(int k, int x, int y)  // x是待修改的点,y是添加的值
{
if(tree[k].l == tree[k].r) // 如果是叶子,也就找到了单个点
{
tree[k].w += y;
return ;
}
int m = (tree[k].l + tree[k].r) >> 1;
if(x <= m) Single_Point_Modification(k << 1,x, y); //如果待修改值x小于中间值,递归左边
else Single_Point_Modification(k << 1 | 1, x, y); //如果待修改值x小于中间值,递归左边
tree[k].w = tree[k << 1].w + tree[k << 1 | 1]. w; //别忘了因为修改这个值,所以需要更新它的上一层的值
}

2、单点查询(Single point query)

void Single_Point_Query(int k, int x)         // 单点查询比较好理解,如果查到了直接返回就好了,所以函数可以写成int类型
{
if(tree[k].l == tree[k].r) // x为需要查询的点
{
ans = tree[k].w;
return ;
}
int m = (tree[k].l + tree[k].r) >> 1;
if(x <= m) Single_Point_Query(k << 1, x);
else Single_Point_Query(k << 1 | 1, x);
}

3、区间查询(Interval query)

void Interval_Query(int k, int x, int y)         //查询区间x~y
{
if(tree[k].l >= x && tree[k].r <= y) // 如果查询的区间在这个范围内,直接加上
{
ans += tree[k].w;
return ;
}
int m = (tree[k].l + tree[k].r) / 2; // 继续递归查询
if(x <= m) Interval_Query(k << 1, x, y);
if(y > m) Interval_Query(k << 1 | 1, x, y);
}

4、区间修改(Interval modification)

在区间修改时会遇到一些问题,如果每次修改都是查询到叶子所在的点来修改,再返回到上一层,依次完成更新,如果是这种操作理论上时间复杂度就是比较高了,如果是查询、修改、查询……这样可能就要QAQ了。可以换一种想法,只有需要查询到修改过的这个区间时,我们才把修改过的这个区间的值算一下,如果没有查询到这个区间,那么这个修改的值就一直存在上一层不动(相当于它的父节点就可以了,如果查询到了,根据这个区间的数的个数把每个数都加上就可以,然后把在父节点的这个标记更新清零),因为用到才会更新的原因吧,所以这个标记就被称作是懒标记了。

如果需要进行区间修改那么结构体中就要多一个变量,来存放需要的那个懒标记。

struct node
{
int l, r; // l,r分别是左右端点
int w; // 用来存放这个区间的值(和)
int f; // 懒标记
};

现在知道了这种思想,那么如果实现的话,首先是这个标记怎么把它传下去呢?

只要是修改的区间包括了k这个点,就需要把懒标记下传下去,然后更新w值,再把标记清零。

void down(int k)
{
tree[k << 1].f += tree[k].f; // 子节点都要加上标记
tree[k << 1 | 1].f += tree[k].f;
tree[k << 1]. w += tree[k].f * (tree[k << 1].r - tree[k << 1].l + 1); // 数值更新
tree[k << 1 |1].w += tree[k].f * (tree[k << 1| 1].r - tree[k << 1| 1].l + 1);
tree[k].f = 0; // “父节点”(懒标记)清零
}

区间修改:

void Interval_modification(int k, int x, int y,int z)  // [x,y]区间上每个值都加上z
{
if(tree[k].l >= x && tree[k].r <= y) //如果全部在这个区间内,那么这个区间的总个数*z的值加在父节点上即可
{
tree[k].w += (tree[k].r - tree[k].l + 1) * z;
tree[k].f += z; // 把标记存在这个地方,如果还可以继续往下访问,利用down来解决
return;
}
if(tree[k].f) down(k); //懒标记下传。
int m = (tree[k].l + tree[k].r) >> 1;
if(x <= m) Interval_modification(k << 1, x, y, z);
if(y > m) Interval_modification(k << 1 | 1, x, y, z);
tree[k].w = tree[k << 1].w + tree[k << 1 | 1].w; // 存放更新后的值
}

当然不喜欢这么多函数,就把懒标记下传写到这里面去就可以了。

合并以后:

void Interval_modification(int k, int x, int y,int z)  // [x,y]区间上每个值都加上z
{
if(tree[k].l >= x && tree[k].r <= y)// 如果全部在这个区间内,那么这个区间的总个数*z的值加在父节点上即可
{
tree[k].w += (tree[k].r - tree[k].l + 1) * z;
tree[k].f += z; // 把标记存在这个地方,如果还可以继续往下访问,利用down来解决
return;
}
if(tree[k].f) //懒标记下传。
{
tree[k << 1].f += tree[k].f; // 子节点都要加上标记
tree[k << 1 | 1].f += tree[k].f;
tree[k << 1]. w += tree[k].f * (tree[k << 1].r - tree[k << 1].l + 1); // 数值更新
tree[k << 1 |1].w += tree[k].f * (tree[k << 1| 1].r - tree[k << 1| 1].l + 1);
tree[k].f = 0; // “父节点”(懒标记)清零 }
int m = (tree[k].l + tree[k].r) >> 1;
if(x <= m) Interval_modification(k << 1, x, y, z);
if(y > m) Interval_modification(k << 1 | 1, x, y, z);
tree[k].w = tree[k << 1].w + tree[k << 1 | 1].w; // 存放更新后的值
}

加上了懒标记之后,相应的单点查询、区间修改都要加上判断懒标记的条件,即:

if(tree[k].f) down(k);  

以上就是线段树的基本操作了,其实如果想加深理解除了手动计算计算,做几道可以更快的理解。

线段树多做做就好啦,QWQ。

线段树QWQ的更多相关文章

  1. [poj3468]A Simple Problem with Integers_线段树

    A Simple Problem with Integers 题目大意:给出n个数,区间加.查询区间和. 注释:1<=n,q<=100,000.(q为操作次数). 想法:嗯...学了这么长 ...

  2. 线段树【p1115】 最大子段和

    题目描述-->p1115 最大子段和 虽然是一个普及-的题,但我敲了线段树 qwq 数组定义 \(lsum[ ]\)代表 该区间左端点开始的最大连续和. \(rsum[ ]\)代表 该区间右端点 ...

  3. Vijos P1066 弱弱的战壕【多解,线段树,暴力,树状数组】

    弱弱的战壕 描述 永恒和mx正在玩一个即时战略游戏,名字嘛~~~~~~恕本人记性不好,忘了-_-b. mx在他的基地附近建立了n个战壕,每个战壕都是一个独立的作战单位,射程可以达到无限(“mx不赢定了 ...

  4. 线段树(单标记+离散化+扫描线+双标记)+zkw线段树+权值线段树+主席树及一些例题

    “队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄>     线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...

  5. 「洛谷3870」「TJOI2009」开关【线段树】

    题目链接 [洛谷] 题解 来做一下水题来掩饰ZJOI2019考炸的心情QwQ. 很明显可以线段树. 维护两个值,\(Lazy\)懒标记表示当前区间是否需要翻转,\(s\)表示区间还有多少灯是亮着的. ...

  6. 洛谷P4770 [NOI2018]你的名字 [后缀自动机,线段树合并]

    传送门 思路 按照套路,直接上后缀自动机. 部分分:\(l=1,r=|S|\) 首先把\(S\)和\(T\)的后缀自动机都建出来. 考虑枚举\(T\)中的右端点\(r\),查询以\(r\)结尾的串最长 ...

  7. 主席树[可持久化线段树](hdu 2665 Kth number、SP 10628 Count on a tree、ZOJ 2112 Dynamic Rankings、codeforces 813E Army Creation、codeforces960F:Pathwalks )

    在今天三黑(恶意评分刷上去的那种)两紫的智推中,突然出现了P3834 [模板]可持久化线段树 1(主席树)就突然有了不详的预感2333 果然...然后我gg了!被大佬虐了! hdu 2665 Kth ...

  8. Luogu 45887 全村最好的嘤嘤刀(线段树 树状数组)

    https://www.luogu.org/problemnew/show/T45887 题目背景 重阳节到了,我们最好的八重樱拥有全村最好的嘤嘤刀…… 题目描述 在绯玉丸力量的影响下,八重村成了一条 ...

  9. cf1136E. Nastya Hasn't Written a Legend(二分 线段树)

    题意 题目链接 Sol yy出了一个暴躁线段树的做法. 因为题目保证了 \(a_i + k_i <= a_{i+1}\) 那么我们每次修改时只需要考虑取max就行了. 显然从一个位置开始能影响到 ...

随机推荐

  1. 为什么用JS取不到cookie的值?解决方法如下!

    注意:cookie是基于域名来储存的.要放到测试服务器上或者本地localhost服务器上才会生效.cookie具有不同域名下储存不可共享的特性.单纯的本地一个html页面打开是无效的. 明明在浏览中 ...

  2. 数据仓库之抽取数据:通过bcp命令行导入数据

    原文:数据仓库之抽取数据:通过bcp命令行导入数据 在做数据仓库时,最重要的就是ETL的开发,而在ETL开发中的第一步,就是要从原OLTP系统中抽取数据到过渡区中,再对这个过渡区中的数据进行转换,最后 ...

  3. 菜鸡之NetCore 使用EF操作数据库 Oracle & Sqlserver (一)

    摘要: 该篇文章主要记录netCore EFCore 如何操作Oracle和SqlServer 数据库,采用Codefirst方式创建数据库以及表. 一, 项目建立 项目采用DDD领域驱动设计模式[学 ...

  4. vscode 使用ESLint 自动检查,保存时自动格式化

    1:全局安装eslint `npm install -g eslint`2: 打开vscode 点击 “文件”----->“首选项”---->“设置”,在右侧“用户设置/settings. ...

  5. 使用CDS view开发SAP Marketing contact的facet追溯工具

    这篇SAP社区博客里,我的一位同事介绍了SAP Marketing里contact facet数据模型的存储表: https://blogs.sap.com/2016/07/01/how-does-s ...

  6. 获取当前日期或者某个日期相隔N天内的全部日期以及星期几

    业务需要需要获取当前日期相隔30天内的全部日期以及星期几,没插件因此特地写了一个: /* 说明:获取当前日期或者某个日期相隔N天内的全部日期以及星期几 使用: let test = new getdi ...

  7. vscode开发

    基于 Electron 开发.typescript编写.底层 Node.js 打造的一个编辑器 , 不是IDE,被称为“披着IDE外衣的编辑器”

  8. Procomm Plus 与ASPECT脚本语言在基于远程终端设备上的测试应用

    产测 ---------------------------------------------------- 原文:http://www.bixuanzl.com/20180801/1084478. ...

  9. 按下开机键,Linux做了什么?

    作者:Vamei 来源:见文末 计算机开机是一个神秘的过程.我们只是按了开机键,就看到屏幕上的进度条或者一行行的输出,直到我们到达登录界面.然而,计算机开机又是个异常脆弱的过程,我们满心期望的登录界面 ...

  10. Selenium(三)webdriver的API与定位元素

    在学习定位元素之前,应该要学会: 1.打开浏览器 2.打开网页 3.定位元素及操作 ①定位元素 可只此输入框的id是kw,name是wd,class是s_ipt ②在python编辑器中找到该元素 通 ...