线段树区间更新(以POJ 3468为例)
来源:互联网 发布:网络拓扑图算法 编辑:程序博客网 时间:2024/06/03 05:19
在了解单点更新的线段树的前提下,再继续理解区间更新的线段树。
区间更新是指更新某个区间内的叶子节点的值,因为涉及到的叶子节点不止一个,而叶子节点会影响其相应的非叶父节点,那么回溯需要更新的非叶子节点也会有很多,如果一次性更新完,操作的时间复杂度肯定不是O(lgn)。为此引入了线段树中的延迟标记概念,这也是线段树的精华所在。
延迟标记:每个节点新增加一个标记,记录该节点是否进行了某种修改。将指定更新区间在树上找到对应节点后,给这些节点打上标记。在查询时,如果当前节点有标记,就给它的子节点打上该标记,并删除当前节点的标记。
延迟标记除了能够记录某个节点的是否进行修改外,通常也记录修改的情况,方便在查询时直接能够得到修改的结果。
struct Node{ ll l,r; ll sum,add;}
对于区间更新的线段树,通常有四个功能:
build:左右递归建树,初始化每个节点sum值。同时将每个节点的延迟标记设置为0。
pushDown:将父节点的延迟标记传递给子节点(如果题意需要,还要将父节点的值传给子节点),同时将父节点的延迟标记清空。
query:对指定区间查询,左右子树递归查询。查询时如果父节点有延迟标记,需要向下传递。
update:对指定区间更新,左右子树递归更新。当更新到指定区间时,加上父节点的延迟标记(如果题意需要,还要进行其他操作),返回。如果没有更新到指定区间,就继续更新,如果过程中有节点有延迟标记,需要向下传递。最后回溯更新父节点的值。
注意:
update时,只有指定区间会有延迟标记,并且此时指定区间和它的父区间的sum已经更改,它的子区间和其他区间没有延迟标记和修改值。
query时,延迟标记会传至子区间,并且同时修改子区间的值,直到找到指定区间。此时,延迟标记在指定区间上,指定区间的子区间的sum值没有修改。
对于POJ 3468:
在pushDown和update时,子区间需要加上父区间传过来的值*区间元素数;
(具体操作见代码)
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>using namespace std;typedef long long ll;const int N=1e5+10;ll a[N];char opr[10];struct Node{ ll l,r; ll sum,add;}tree[4*N];void build(ll i,ll l,ll r){ tree[i].l=l,tree[i].r=r; tree[i].add=0; if(l==r){//左边=右边,单点建立 tree[i].sum=a[l]; return; } ll tmp=i<<1; ll mid=(l+r)>>1; build(tmp,l,mid);//左子树 build(tmp+1,mid+1,r);//右子树 tree[i].sum=tree[tmp].sum+tree[tmp+1].sum;//回溯得到区间sum值}void pushDown(ll x){ ll tmp=x<<1; tree[tmp].add+=tree[x].add;//左子树得到父节点标记 tree[tmp+1].add+=tree[x].add;//右子树得到父节点标记 //如果区间内每个元素加上一个值,那么这个区间会加上当前区间包含指定区间元素个数*待加元素的值 tree[tmp].sum+=tree[x].add*(tree[tmp].r-tree[tmp].l+1); tree[tmp+1].sum+=tree[x].add*(tree[tmp+1].r-tree[tmp+1].l+1); tree[x].add=0;//清空父节点标记}ll query(ll i,ll l,ll r){ if(tree[i].l>r||tree[i].r<l){//超出范围 return 0; } if(tree[i].l>=l&&tree[i].r<=r){//刚好在落到求得的区间 return tree[i].sum; } //如果查询时发现当前区间不为目标区间,但是有延迟标记时,将延迟标记传递 if(tree[i].add) pushDown(i); ll tmp=i<<1; //左右子树分别查询,将查询结果相加 //如果某一子树超出范围,会在第一个条件处return 0 return query(tmp,l,r)+query(tmp+1,l,r);}void update(ll i,ll l,ll r,ll s){ if(tree[i].l>r||tree[i].r<l){//超出范围 return ; } //在指定区间的范围内,该点加上sum与延迟标记.此时包含该点的父区间均更新完毕 //>=、<=是考虑到区间位于不同子树的情况 if(tree[i].l>=l&&tree[i].r<=r){ tree[i].add+=s; tree[i].sum+=s*(tree[i].r-tree[i].l+1); return; } //如果之前已经有延迟标记,需要将之前的标记处理 if(tree[i].add) pushDown(i); ll tmp=i<<1; //更新左右子树,如果某个子树超出了所更新区间的范围,会在第一个条件处return update(tmp,l,r,s); update(tmp+1,l,r,s); tree[i].sum=tree[tmp].sum+tree[tmp+1].sum;//回溯更新父节点的值 }int main(){ int n,q; while(~scanf("%d %d",&n,&q)){ for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); } build(1,1,n); ll l,r,s; while(q--){ scanf("%s",opr); if(opr[0]=='Q'){ scanf("%lld %lld",&l,&r); cout<<query(1,l,r)<<endl; } else if(opr[0]=='C'){ scanf("%lld %lld %lld",&l,&r,&s); update(1,l,r,s); } } }}/*10 51 2 3 4 5 6 7 8 9 10Q 4 4Q 1 10Q 2 4C 3 6 3Q 2 45 101 2 3 4 5C 3 5 2Q 3 5*/
- 线段树区间更新(以POJ 3468为例)
- 非递归线段树区间修改区间求和的两种实现(以POJ 3468为例)
- POJ 3468 线段树(区间更新)
- POJ-3468(线段树区间更新区间查询)
- POJ 3468 线段树 区间更新
- POJ-3468(线段树_区间更新)
- 线段树 : 区间更新 poj 3468 示例
- 线段树 : 区间更新 poj 3468 示例
- poj 3468 线段树区间更新维护
- poj 3468 线段树区间更新
- poj 3468(简单线段树区间更新)
- POJ 3468 线段树区间更新
- POJ 3468 线段树区间更新
- poj 3468 线段树区间更新
- POJ 3468(线段树区间更新)
- POJ 3468 线段树 区间更新
- poj-3468-线段树,区间更新
- Poj 3468 线段树的区间更新
- 总结71+1
- js中闭包问题
- 网络规划总结
- BZOJ 1076 [SCOI2008]奖励关
- vtk程序的cmake两种方法
- 线段树区间更新(以POJ 3468为例)
- spring配置文件比较全的约束
- Impala负载均衡方案
- React 入门实例教程
- android 字符串截取
- [51nod1223]分数等式的数量
- JAVA 编译找不到符号
- java--基础类型
- 客服