标记永久化是线段树的另一种写法,顾名思义,就是让懒标记永久作用在结点上不下传。
回顾一下下传标记的写法。对于一个结点,懒标记作用于其管辖的范围。换句话说,其所有子孙结点都会被懒标记作用恰好一次。在进入下一层时,我们先将懒标记作用于其儿子,然后再将懒标记和其儿子的懒标记合并。所以普通线段树需要满足信息可合并。
既然懒标记是在进入下一层的时候才传的,那么之前肯定已经经过了所有能影响当前结点儿子的结点。所以我们不妨在查询的时候事先统计出当前懒标记对查询区间的影响。这就要求操作满足结合律,因为这样无法得知操作的先后。
实现的时候有一个细节,必须保证修改或查询的区间被当前结点所管辖的区间包含。这样我们才能方便地统计贡献。
下面放一下区间加区间求和的代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define ls(p)((p)<<1)
#define rs(p)((p)<<1|1)
struct Segment_Tree
{
int l,r;
ll tag,pre;
}t[400005];
ll read()
{
ll A;
bool K;
char C;
C=A=K=0;
while(C<'0'||C>'9')K|=C=='-',C=getchar();
while(C>'/'&&C<':')A=(A<<3)+(A<<1)+(C^48),C=getchar();
return(K?-A:A);
}
inline void pushup(int p)
{
t[p].pre=t[ls(p)].pre+t[rs(p)].pre;
}
void build(int p,int l,int r)
{
t[p].l=l;
t[p].r=r;
if(l==r)
{
t[p].pre=read();
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid),build(rs(p),mid+1,r);
pushup(p);
}
void change(int p,int x,int y,ll z)
{
t[p].pre+=(y-x+1)*z;
if(x<=t[p].l&&t[p].r<=y)
{
t[p].tag+=z;
return;
}
if(y<=t[ls(p)].r)change(ls(p),x,y,z);
else if(x>=t[rs(p)].l)change(rs(p),x,y,z);
else change(ls(p),x,t[ls(p)].r,z),change(rs(p),t[rs(p)].l,y,z);
}
ll ask(int p,int x,int y)
{
if(x<=t[p].l&&t[p].r<=y)return t[p].pre;
ll ret=t[p].tag*(y-x+1);
if(y<=t[ls(p)].r)ret+=ask(ls(p),x,y);
else if(x>=t[rs(p)].l)ret+=ask(rs(p),x,y);
else ret+=ask(ls(p),x,t[ls(p)].r)+ask(rs(p),t[rs(p)].l,y);
return ret;
}
int main()
{
int n,m,opt,x,y;
n=read(),m=read();
build(1,1,n);
while(m--)
{
opt=read(),x=read(),y=read();
if(opt==1)change(1,x,y,read());
else printf("%lld
",ask(1,x,y));
}
return 0;
}