【线段树-区间更新】知识点讲解 + 模板题

知识点讲解

博文(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;
} 


模板题(区间赋值更新)

【线段树-区间更新】知识点讲解 + 模板题_第1张图片

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即可。

你可能感兴趣的:(蓝桥杯)