线段树总结
Posted 勿忘初心0924
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树总结相关的知识,希望对你有一定的参考价值。
线段树总结
——这个周末训练赛和codeforces,加上自己有点偷懒导致进度严重推迟
线段树,顾名思义是在树上的线段,通过建树来维护你需要的操作,基本的操作有:区间求和,区间求最值,区间异或(这个实际上和区间更新差不多,就是加上值这个操作换成了异或),区间覆盖,扫描线求面积,线段树求区间连续字段。
下面从最基础的区间求最值开始:
给你一个长度为n的序列,例如长度为5的序列{9,1,8,5,6},最大值和最小值,当然朴素算法在数据量小的时候是可以的,但是在n的数量级特别大的时候就显得略加笨重。由此想出一种办法,将数组里的数构成一棵数。如下图:
这棵树中叶子保存的是每个下标的数值,两个叶子的父亲,保存的是叶子中最小的那一个,这样一直到根节点得出数列中最小值。最大值也是一样。说起来很简单,接着就是用代码实现,线段树是一个二叉树。数据量大的时候也可能是一个完全二叉树,用一个sum数组来存储每个节点的数值,然后递归建树。
void build(int i,int l,int r) { if(l==r) { scanf("%d",&sum[i]); return ; } int m=(l+r)/2; build(i*2,l,m); build(i*2+1,m+1,r); pushup(i);//收集子节点的结果 } Pushup()函数是将当前节点向下更新 void pushup(int i) { sum[i]=min(sum[i*2],sum[i*2+1]); }
当维护用途不同的时候push函数的用法是不一样的。下面每种用途的线段树push函数的写法都有讲解。
然后是更新操作:
void update(int id ,int val,int i,int l,int r) { if(l==r) { sum[i]=val;//这里的操作的修改id点的值 return; } int m=(l+r)/2; if(id<=m) update(id,val,i*2,l,m); else update(id,val,i*2+1,m+1,r); pushup(i); }
修改,查询操作都是从根节点开始遍历,然后当你遍历到的当前区间,在需要区间之内的时候,就可以进行你需要的操作了。
查询操作:
查询的时候,一个区间的最值要不就在左区间,要不就在右区间,要不然就在左加右区间(虽然很像废话,但是就是这样的)
int query (int rt,int L,int R,int l,int r) { if(L<=l&&r<=R) return sum[rt]; int m=(r+l)>>1; int ret=0; if(L<=m) ret=min(ret,query(rt*2,L,R,l,m) if(R>m) ret=min(ret,query(rt*2+1,L,R,m+1,r); return ret; }
区间求和(单点更新,区间更新):
单点更新:
int sum[N*4]; void pushup(int i) { sum[i]=sum[i*2]+sum[i*2+1]; } void build(int i,int l,int r) { if(l==r) { scanf("%d",&sum[i]); return ; } int m=(l+r)/2; build(i*2,l,m); build(i*2+1,m+1,r); pushup(i);//收集子节点的结果 } /* 在当前区间[l, r]内查询区间[ql, qr]间的目标值 且能执行这个函数的前提是:[l,r]与[ql,qr]的交集非空 其实本函数返回的结果也是 它们交集的目标值 */ int query(int ql,int qr,int i,int l,int r) { if(ql<=l&&r<=qr) return sum[i]; int m=(l+r)/2; int cur=0; if(ql<=m) cur+=query(ql,qr,i*2,l,m); if(m<qr) cur+=query(ql,qr,i*2+1,m+1,r); return cur; } /* update这个函数就有点定制的意味了 本题是单点更新,所以是在区间[l,r]内使得第id数的值+val 如果是区间更新,可以update的参数需要将id变为ql和qr */ void update(int id ,int val,int i,int l,int r) { if(l==r) { sum[i]+=val; return; } int m=(l+r)/2; if(id<=m) update(id,val,i*2,l,m); else update(id,val,i*2+1,m+1,r); pushup(i); }
区间更新:
这里要引进一个概念叫:延迟更新。当你想要更新一个区间的时候人进一个数组addv,addv[i]表示以i为节点的树共同增加了(addv[i]),然后在通过递归,顺带更新带叶子结点。
const int MAXN=100000+100; typedef long long LL; #define lson i*2,l,m #define rson i*2+1,m+1,r LL sum[MAXN*4]; LL addv[MAXN*4]; void PushDown(int i,int num)//这就是延迟操作,更新当前结点的叶子 { if(addv[i]) { sum[i*2] +=addv[i]*(num-(num/2));//每个点的需要更新的值乘以的个数 sum[i*2+1] +=addv[i]*(num/2);//同上 addv[i*2] +=addv[i];//这个区间需要更新的个数 addv[i*2+1]+=addv[i]; addv[i]=0; } } void PushUp(int i) { sum[i]=sum[i*2]+sum[i*2+1]; } void build(int i,int l,int r) { addv[i]=0;//将延迟操作更改的值需要记录到addv数组中,现在将它初始化 if(l==r) { scanf("%I64d",&sum[i]); return ; } int m=(l+r)/2; build(lson); build(rson); PushUp(i); } void update(int ql,int qr,int add,int i,int l,int r) { if(ql<=l&&r<=qr) { addv[i]+=add; sum[i] += (LL)add*(r-l+1); return ; } PushDown(i,r-l+1);//向下更新枝叶的值 int m=(l+r)/2; if(ql<=m) update(ql,qr,add,lson); if(m<qr) update(ql,qr,add,rson); PushUp(i); } LL query(int ql,int qr,int i,int l,int r) { if(ql<=l&&r<=qr) { return sum[i]; } PushDown(i,r-l+1); int m=(l+r)/2; LL res=0; if(ql<=m) res+=query(ql,qr,lson); if(m<qr) res+=query(ql,qr,rson); return res; }
以上是关于线段树总结的主要内容,如果未能解决你的问题,请参考以下文章