对线段树进行区间更新时,采用一般方法需要访问区间每一个元素对应的所有叶节点 并逐级向上更新,这一操作的复杂度可以达到
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),比朴素方法还要慢。因此引入懒标记,更新时更新到能包含整个区间的结点时就给那个点打上标记,需要查询时再下传到左右儿子,这样可以显著提升效率。
懒标记讲解
对某个节点的懒标记进行下放时,左右儿子的标记也要更新,并且该节点的标记值要清零,以防重复下放。
用懒标记解决洛谷P3372:
#include <cstdio>
#include <algorithm>
#define long long long
const int M=(int)1e5+1;
using namespace std;
long dat[M*4],tag[M*4]; //tag为懒标记,存储曾经加上过的值备用
int n,num;
inline void lazy_tag(int k,int l,int r) {
if(tag[k]) {
//下放到左右儿子
int left=k*2+1,right=k*2+2;
int child_len=(r-l+1)/2; //求子区间的长度
dat[left]+=tag[k]*child_len;
dat[right]+=tag[k]*child_len;
tag[left]+=tag[k];
tag[right]+=tag[k]; //记得为左右打上标记
//清零,防止重复下放
tag[k]=0;
}
}
void update(int st,int end,long val,int k=0,int l=0,int r=n) {
//当前点对应的区间不合法
if(l>end||r<st) return;
if(l>=st&&r<=end) {
dat[k]+=val*(long)(r-l+1);
tag[k]+=val; //注意打标记
return;
}
//防止之前有修改过的值,因此update也要将懒标记下放
//不需要一次性下放到叶节点,遍历到需要的点时会再次下放
lazy_tag(k,l,r);
//计算左右加上的大小
int lc=k*2+1,rc=k*2+2;
update(st,end,val,lc,l,(l+r)/2);
update(st,end,val,rc,1+(l+r)/2,r);
dat[k]=dat[lc]+dat[rc]; //更新
}
long query(int st,int end,int k=0,int l=0,int r=n) {
if(l>end||r<st) return 0;
if(l>=st&&r<=end) return dat[k];
lazy_tag(k,l,r);
int lc=k*2+1,rc=k*2+2;
long L=query(st,end,lc,l,(l+r)/2);
long R=query(st,end,rc,1+(l+r)/2,r);
return L+R;
}
int main(){
int m;
scanf("%d%d",&n,&m);
int i,t=n;
for(i=0; (1<<i)<n; i++);
num=(1<<(i+1))-1;
n=(1<<i)-1; //更新n的值为2的整数次幂方便使用
for(int i=0; i<t; i++) {
long x;
scanf("%lld",&x);
update(i,i,x);
}
fill(tag,tag+num,0);
while(m--) {
short op;
scanf("%hd",&op);
if(op==1) {
int x,y;
long k;
scanf("%d%d%lld",&x,&y,&k);
update(x-1,y-1,k);
}
else {
int x,y;
scanf("%d%d",&x,&y);
printf("%lld\n",query(x-1,y-1));
}
}
return 0;
}