线段树
来源:互联网 发布:马里亚纳网络为何恐怖 编辑:程序博客网 时间:2024/06/05 17:46
- 线段树是什么
- 怎么构建
- 模板
- 例题
该来的还是要来的.理解了树状数组之后想要解决区间更新及询问的问题,就要用到线段树.当然非常骚的树状数组可以过,不过那就是大佬做的事了.线段树其实不是一个很难的东西,因为递归二分的原因,除了push_down操作比较费事,其它都很好背下来.只要理解一点点就好.
线段树是什么
先引入一个题:洛谷模板题3372.
就是给一个数组,有两种操作:给一个区间的每个数加k,或让你求一个区间的和.
暴力! n*m,gg.那肯定不行.但是用线段树可以.它的预处理复杂度nlogn,操作复杂度logn,空间复杂度n,就能过了.
我再一次作为灵魂画师为大家讲解一下线段树.假设有一个数组a[11],存储数据的位置为1-10.
我又画得很骚,不要在意.
也就是说线段树就是一棵完全二叉树,它的每个叶子结点存储数组元素的值,而它的父节点是两个子节点的和.一共有2*n-1个节点,所以处理的时候数组要用2xN的大小,甚至有些时候要开到4xN.
怎么构建
好的.首先我们先建立线段树.可以知道,每一棵子树根节点是segtree[i],左孩子是segtree[i*2],右孩子是segtree[i*2+1].我们可以用位运算加速计算左孩子和右孩子.
据此可写出函数build.
#define mid ((istart+iend)>>1)#define lson root<<1,istart,mid#define rson root<<1|1,mid+1,iendstruct node{ll value,lazy;}segtree[boss*4+10];void build(ll a[],int root,int istart,int iend){segtree[root].lazy=0;//先不要管这个lazy是什么,只看下面if (istart==iend) segtree[root].value=a[istart];else { build(a,lson),build(a,rson);//建立左孩子,建立右孩子 segtree[root].value=segtree[root<<1].value+segtree[root<<1|1].value; }}
不会也不要紧,背出之.
然后是询问操作.
ll query(int root,int istart,int iend,int qstart,int qend){if (qstart>iend||qend<istart) return 0;//qstart,qend如果不在范围内询问失败if (qstart<=istart&&qend>=iend) return segtree[root].value;//在范围内,询问出根节点的值push_down(root,istart,iend);return query(lson,qstart,qend)+query(rson,qstart,qend);//递归左孩子右孩子}
诶?这个push_down是什么玩意?
inline void push_down(int root,int istart,int iend)//先不解释.{if (segtree[root].lazy!=0) { segtree[root<<1].lazy+=segtree[root].lazy; segtree[root<<1|1].lazy+=segtree[root].lazy; segtree[root<<1].value+=segtree[root].lazy*(mid-istart+1); segtree[root<<1|1].value+=segtree[root].lazy*(iend-mid); segtree[root].lazy=0; }}
接下来才是高潮呢!终于要解释lazy是什么了.
为了更新区间,线段树中引入了延迟标记这个概念.这才是线段树的精华所在.
每个节点新增加一个标记,记录这个节点是否进行了某种修改(这种修改操作会影响其子节点),对于任意区间的修改,我们先按照区间查询的方式将其划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那么我们就要看节点p是否被标记,如果有,就要按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时消掉节点p的标记。
区间更新举例说明:当我们要对区间[0,2]的叶子节点增加2,利用区间查询的方法从根节点开始找到了非叶子节点[0-2],把它的值设置为1+2 = 3,并且把它的延迟标记设置为2,更新完毕;当我们要查询区间[0,1]内的最小值时,查找到区间[0,2]时,发现它的标记不为0,并且还要向下搜索,因此要把标记向下传递,把节点[0-1]的值设置为2+2 = 4,标记设置为2,节点[2-2]的值设置为1+2 = 3,标记设置为2(其实叶子节点的标志是不起作用的,这里是为了操作的一致性),然后返回查询结果:[0-1]节点的值4;当我们再次更新区间[0,1](增加3)时,查询到节点[0-1],发现它的标记值为2,因此把它的标记值设置为2+3 = 5,节点的值设置为4+3 = 7;
//此处摘至http://blog.sina.com.cn/s/blog_a2dce6b30101l8bi.html
这样的话更新操作时,当父节点的延迟标记不为0,我们可以把它传到子节点,然后消掉父节点的标记,直到变成单点修改,标记消失.非常神奇吧?
解释完了以后就可以看区间更新了.
void update(int root,int istart,int iend,int ustart,int uend,int k){if (ustart>iend||uend<istart) return;if (ustart<=istart&&uend>=iend) { segtree[root].lazy+=k; segtree[root].value+=k*(iend-istart+1); return; }push_down(root,istart,iend);update(lson,ustart,uend,k);update(rson,ustart,uend,k);segtree[root].value=segtree[root<<1].value+segtree[root<<1|1].value;}//应该不需要我再说明了.
模板
最后是AC洛谷3372模板的代码.
#include<stdio.h>#define boss 100000#define mid ((istart+iend)>>1)#define lson root<<1,istart,mid#define rson root<<1|1,mid+1,iendusing namespace std;typedef long long ll;struct node{ll value,lazy;}segtree[boss*4+10];ll a[boss+10];int n,m,t,x,y,k; void build(ll a[],int root,int istart,int iend){segtree[root].lazy=0;if (istart==iend) segtree[root].value=a[istart];else { build(a,lson),build(a,rson); segtree[root].value=segtree[root<<1].value+segtree[root<<1|1].value; }}inline void push_down(int root,int istart,int iend){if (segtree[root].lazy!=0) { segtree[root<<1].lazy+=segtree[root].lazy; segtree[root<<1|1].lazy+=segtree[root].lazy; segtree[root<<1].value+=segtree[root].lazy*(mid-istart+1); segtree[root<<1|1].value+=segtree[root].lazy*(iend-mid); segtree[root].lazy=0; }}ll query(int root,int istart,int iend,int qstart,int qend){if (qstart>iend||qend<istart) return 0;if (qstart<=istart&&qend>=iend) return segtree[root].value;push_down(root,istart,iend);return query(lson,qstart,qend)+query(rson,qstart,qend);}void update(int root,int istart,int iend,int ustart,int uend,int k){if (ustart>iend||uend<istart) return;if (ustart<=istart&&uend>=iend) { segtree[root].lazy+=k; segtree[root].value+=k*(iend-istart+1); return; }push_down(root,istart,iend);update(lson,ustart,uend,k);update(rson,ustart,uend,k);segtree[root].value=segtree[root<<1].value+segtree[root<<1|1].value;}int main(){int i;scanf("%d%d",&n,&m);for (i=1;i<=n;i++) scanf("%lld",&a[i]);build(a,1,1,n);for (i=1;i<=m;i++) { scanf("%d",&t); if (t==1) { scanf("%d%d%d",&x,&y,&k); update(1,1,n,x,y,k); } else if (t==2) { scanf("%d%d",&x,&y); printf("%lld\n",query(1,1,n,x,y)); } }}
在写这篇博客之前我也不知道延迟标记到底是什么,当我解释完了之后发现自己会了.
例题
我看看有没有线段树的例题.有几个.
我再引用一下别人的博客吧.
线段树经典例题
- 线段树?线段树!
- 线段树?线段树!
- 线段_线段树
- 线段_线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- Android安卓——Android程序生命周期
- sublime3,加入Package Control
- 前端学习第九弹:简易的聊天界面
- SQL Server 和 Oracle 以及 MySQL 有哪些区别?
- 去除textarea右下角的箭头(禁止拉伸)
- 线段树
- ping和Telnet
- 顺序表的实现
- ubuntu16.04下安装NVIDIA(cuda)-gtx965m相关步骤以及问题
- Lua语法分析(2)- 控制语句
- 你经历过绝望吗?两次! CSU
- Ionic3 Tips
- 2017/11/5模拟赛总结
- 十进制转二进制、二进制转十进制