知识点讲解:
博文(https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html)讲得很通俗易懂,copy一下:
5、区间修改,即修改一段连续区间的值,我们已给区间[a,b]的每个数都加x为例讲解
Ⅰ.引子
有人可能就想到了:
修改的时候只修改对查询有用的点。
对,这就是区间修改的关键思路。
为了实现这个,我们引入一个新的状态——懒标记。
Ⅱ 懒标记
(懒标记比较难理解,我尽力讲明白。。。。。。)
1、直观理解:“懒”标记,懒嘛!用到它才动,不用它就睡觉。
2、作用:存储到这个节点的修改信息,暂时不把修改信息传到子节点。就像家长扣零花钱,你用的时候才给你,不用不给你。
3、实现思路(重点):
a.原结构体中增加新的变量,存储这个懒标记。
b.递归到这个节点时,只更新这个节点的状态,并把当前的更改值累积到标记中。注意是累积,可以这样理解:过年,很多个亲戚都给你压岁钱,但你暂时不用,所以都被你父母扣下了。
c.什么时候才用到这个懒标记?当需要递归这个节点的子节点时,标记下传给子节点。这里不必管用哪个子节点,两个都传下去。就像你如果还有妹妹,父母给你们零花钱时总不能偏心吧
d.下传操作:
3部分:①当前节点的懒标记累积到子节点的懒标记中。
②修改子节点状态。在引例中,就是原状态+子节点区间点的个数*父节点传下来的懒标记。
这就有疑问了,既然父节点都把标记传下来了,为什么还要乘父节点的懒标记,乘自己的不行吗?
因为自己的标记可能是父节点多次传下来的累积,每次都乘自己的懒标记造成重复累积
③父节点懒标记清0。这个懒标记已经传下去了,不清0后面再用这个懒标记时会重复下传。就像你父母给了你5元钱,你不能说因为前几次给了你10元钱, 所以这次给了你15元,那你不就亏大了。
算法代码模板:
注意,以下模板是对于“区间赋值更新”的,所以子节点直接等于下传的lazy值,且lazy值无需叠加(即是=而非+=)
如果是“区间加减更新”,显然,你lazy值应该是要叠加的(不懂的话再看上面讲解),你的子节点也应该是加上下传的lazy值。代码中注释了一下,在哪些地方应把“=”换成“+=”。
void up(int p)
{
s[p] = s[p * 2] + s[p * 2 + 1];
}
void down(int p, int l, int r)
{
if (col[p])
{
int mid = (l + r) / 2;
s[p * 2] = col[p] * (mid - l + 1); //+=
s[p * 2 + 1] = col[p] * (r - mid); //+=
col[p * 2] = col[p * 2 + 1] = col[p]; //+=
col[p] = 0;
}
}
void modify(int p, int l, int r, int x, int y, int c) //c为赋值更新还是加减更新
{
if (x <= l && r <= y)
{
s[p] = (r - l + 1) * c; //仅修改该结点 //+=
col[p] = c; //增加标记,子结点待修改 //+=
return;
}
down(p, l, r); //下传lazy标记
int mid = (l + r) / 2;
if (x <= mid) modify(p * 2, l, mid, x, y, c);
if (y > mid) modify(p * 2 + 1, mid + 1, r, x, y, c);
up(p);
}
注意:
因为引入了懒标记,很多用不着的更改状态存了起来,这就会对区间查询、单点查询造成一定的影响。
所以在使用了懒标记的程序中,单点查询、区间查询也要像区间修改那样,对用得到的懒标记下传。
以区间查询为例:
long long getsum(int p,int l,int r,int x,int y)
{
if(x<=l && y>=r)
return s[p];
down(p,l,r); //单点查询和区间查询也要下传lazy值
long long res=0;
int mid=(l+r)/2;
if(x<=mid) res+=getsum(p*2,l,mid,x,y);
if(y>mid) res+=getsum(p*2+1,mid+1,r,x,y);
return res;
}
模板题(区间赋值更新):
AC代码:
#include
#include
using namespace std;
const int maxn=4e5+10;
int s[maxn];
int col[maxn]; //保存lazy值
void down(int p,int l,int r)
{
if(col[p]!=0)
{
int mid=(l+r)/2;
s[p*2]=col[p]*(mid-l+1);
s[p*2+1]=col[p]*(r-mid);
col[p*2]=col[p*2+1]=col[p];
col[p]=0;
}
}
void up(int p)
{
s[p]=s[p*2]+s[p*2+1];
}
void renew(int p,int l,int r,int x,int y,int v)
{
if(x<=l&&y>=r)
{
s[p]=(r-l+1)*v;
col[p]=v;
return;
}
down(p,l,r);
int mid=(l+r)/2;
if(x<=mid) renew(p*2,l,mid,x,y,v);
if(y>mid) renew(p*2+1,mid+1,r,x,y,v);
up(p);
}
/*
int getsum(int p,int l,int r,int x,int y)
{
if(x<=l && y>=r)
return s[p];
int res=0;
int mid=(l+r)/2;
if(x<=mid) res+=getsum(p*2,l,mid,x,y);
if(y>mid) res+=getsum(p*2+1,mid+1,r,x,y);
return res;
} */
int main()
{
int n,q;
cin>>n>>q;
renew(1,1,n,1,n,1);
int x,y,z;
while(q--)
{
scanf("%d%d%d",&x,&y,&z);
renew(1,1,n,x,y,z);
}
printf("The total value of the hook is %d.\n",s[1]); //蠢了。。
return 0;
}
蠢了的那里是因为我还想着去用getsum把查询区间开到1~n去求呢。。完全不需要,根据线段树的二分结构以及这是一个存储区间和的线段树... s[1]就是所有点的和!
另外想提一点,其实区间更新也可以搞单点更新诶,尤其是初始化赋值的时候就不用再另写个单点更新函数了,把区间x y设成相同的i即可。