首页 > 其他分享 >线段树区间查改(懒标记+代码细节)

线段树区间查改(懒标记+代码细节)

时间:2023-07-05 15:00:16浏览次数:56  
标签:lazy 标记 int 线段 tree num 查改 sum 节点

  就如同我上次写链式前向星一样,这次我又一次在模拟赛中打算混点分。经过我缜密的思考基于暴力的猜测,我认为带懒操作的线段树至少可以混70分!(大雾弥漫)。于是我兴冲冲的开始敲代码,然后……线段树就打挂了……比赛结束后我痛定思痛,决定要好好复习一下线段树,然后经过我一下午的折腾,我终于……陷在bug的泥沼中出不来了……总之,我一直折腾到了晚上,才把代码完善(至少能跑了),那么今天就来讲讲关于线段树的懒操作的一些东西吧

-----------------------------------------------分割线---------------------------------------------------------

一,懒操作

  关于线段树最基础的内容,我在去年还是前年抑或是大前年的时候就有提及(线段树初步:建树、单点查改、区间查询)这里就不作详细介绍了

  在之前那篇文章里面我提到了线段树的单点查改,而假如我们用同样的方法进行区间修改,不用计算就知道这个复杂度简直难以想象,但是我们在用这种笨拙的方式进行区间修改时可以发现一些有趣的规律

  举个例子,a[15]={0,1,2,3,4,5,6,7,8,9,10};我们要将a[3]~a[6]集体加上5,考虑一个一个进行单点修改,修改路径依次如下(从左往右为单点修改a[3]~a[6])

  然后我们就发现了这样一个有趣的事,在修改a[4],a[5]的时候,他们的路径大致相同,只是在第5个节点处分叉分别去处理4和5。

  这让我们不禁想到一个优化,我们能不能在区间查改递归到第五个节点时给这个节点上个标记,表示该节点范围内的所有元素都要被修改,然后等到我们需要查询第五个节点的子节点的时候,再把这个标记下放,这样就可以省去修改时对节点10 11的递归了

  没错!这个优化就叫懒操作,只要暂时用不到就不去处理,真的很懒()

  懒操作是线段树的精髓,没有懒操作的线段树只是个花架子,有了懒操作之后,线段树就有了高效的区间查询修改的能力

  那么我们该如何实现懒操作呢

  重点就在于懒标记

二、懒标记

    先来看看以前说过的单点查改线段树的是怎么存的

1 const int mm=100005;
2 struct tree{
3     int l,r;  //左边界和右边界(left,right)
4     int sum;  //存的是l,r这个区间的和
5 }a[mm*4];

  现在我们给每个节点加上一个lazy标签,表示这个点的懒标记,lazy=1就表示该节点范围内的所有元素都应增加1,lazy=2就表示该节点范围内的所有元素都应增加2,以此类推。如果说lazy=0,那么就说明该节点没有被打上懒标记

  每个节点如下

struct node{
	long long l,r,lazy,sum;
}tree[mm*4];

  然后我们来考虑这样一个事情,就是什么时候应该打懒标记,什么时候应当下放懒标记

  让我们回到修改a[4]a[5]的这张图

  显然按照我们刚刚的思路,第五个节点是应当被打上懒标签的,也就是说如果该节点的范围在区间修改的范围以内,就可以给这个节点打上懒标签回溯了

  那假如现在要修改a[5]应当怎么做呢,显然第五个节点上的懒标记已经无法胜任这个任务了,那就将懒标记下放,所谓下放懒标记就是让左子节点和右子节点的lazy标签加上该节点的lazy标签,然后将该节点的lazy标签归零

  代码实现如下

void down(int num)
{
	tree[num*2].lazy+=tree[num].lazy;
	tree[num*2].sum+=(tree[num*2].r-tree[num*2].l+1)*tree[num].lazy;
	tree[num*2+1].lazy+=tree[num].lazy;
	tree[num*2+1].sum+=(tree[num*2+1].r-tree[num*2+1].l+1)*tree[num].lazy;
	tree[num].lazy=0;
}

  关于懒标记的内容已经介绍完毕了,现在应当将它放入线段树了

三、区间查询修改

  洛谷上的线段树模板题

  这个板块主要考虑一下关于实现的代码细节

  建树部分不用多讲,和不带懒操作的没什么不同

  区间修改需要注意如果某个节点的范围完全在修改范围内,给这个节点打上标记,sum加上(r-l+1)*增加的数值,即这个节点的sum增加的总值

  区间修改回溯的时候每个节点的sum仍然是两个子节点sum之和,代码实现如下

void upd(int l,int r,int ad,int num)//左边界 右边界 增加值 该节点编号 
{
	if(tree[num].l>r||tree[num].r<l) return ;//如果该节点范围与修改范围完全没有交集就没必要向下递归了 
	if(tree[num].l>=l&&tree[num].r<=r)//如果节点的范围在修改范围内,打上懒标签,修改sum值,回溯 
	{
		tree[num].lazy+=ad;
		tree[num].sum+=(tree[num].r-tree[num].l+1)*ad;
		return ;
	}
	if(tree[num].lazy>0) down(num);//如果该节点有懒标签,下放标签 
	upd(l,r,ad,num*2);//递归左子树 
	upd(l,r,ad,num*2+1);//递归右子树 
	tree[num].sum=tree[num*2].sum+tree[num*2+1].sum;//更新sum 
}

  查询的方法也类似,只不过不需要修改节点元素,代码实现如下

long long sech(int l,int r,int num)//左边界 有边界 该节点编号 
{
	if(tree[num].l>=l&&tree[num].r<=r) //跟不带懒操作的线段树一样 
	{
		return tree[num].sum; 
	}
	if(tree[num].l>r||tree[num].r<l) 
	{
		return 0;
	}
	if(tree[num].lazy>0) down(num);//下发标签 
	return sech(l,r,num*2)+sech(l,r,num*2+1);//查询左右子树 
}

  (题目好像也只要求做区间查改)

  完整代码如下

#include<bits/stdc++.h>
using namespace std;
const int mm=1000005;
int n,m,a[mm];
struct node{
	long long l,r,lazy,sum;
}tree[mm*4];
void build(int l,int r,int num)//建树 
{
	tree[num].l=l;
	tree[num].r=r;
	if(l==r)
	{
		tree[num].sum=a[l];
		return ;
	}
	int mid=(l+r)/2;
	build(l,mid,num*2);
	build(mid+1,r,num*2+1);
	tree[num].sum=tree[num*2].sum+tree[num*2+1].sum;
	return ;
}
void down(int num)//下放标签 
{
	tree[num*2].lazy+=tree[num].lazy;//将lazy下放到左子树 
	tree[num*2].sum+=(tree[num*2].r-tree[num*2].l+1)*tree[num].lazy;//更新左子树的sum 
	tree[num*2+1].lazy+=tree[num].lazy;//下放到右子树 
	tree[num*2+1].sum+=(tree[num*2+1].r-tree[num*2+1].l+1)*tree[num].lazy;//更新右子树的sum 
	tree[num].lazy=0;//下放玩后该节点便不再有懒标记 
}
void upd(int l,int r,int ad,int num)//左边界 有边界 增加值 该节点编号 
{
	if(tree[num].l>r||tree[num].r<l) return ;//如果该节点范围与修改范围完全没有交集就没必要向下递归了 
	if(tree[num].l>=l&&tree[num].r<=r)//如果节点的范围在修改范围内,打上懒标签,修改sum值,回溯 
	{
		tree[num].lazy+=ad;
		tree[num].sum+=(tree[num].r-tree[num].l+1)*ad;
		return ;
	}
	if(tree[num].lazy>0) down(num);//如果该节点有懒标签,下放标签 
	upd(l,r,ad,num*2);//递归左子树 
	upd(l,r,ad,num*2+1);//递归右子树 
	tree[num].sum=tree[num*2].sum+tree[num*2+1].sum;//更新sum 
}
long long sech(int l,int r,int num)//左边界 有边界 该节点编号 
{
	if(tree[num].l>=l&&tree[num].r<=r) //跟不带懒操作的线段树一样 
	{
		return tree[num].sum; 
	}
	if(tree[num].l>r||tree[num].r<l) 
	{
		return 0;
	}
	if(tree[num].lazy>0) down(num);//下发标签 
	return sech(l,r,num*2)+sech(l,r,num*2+1);//查询左右子树 
}
int main()
{
	//freopen("in.txt","r",stdin);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,n,1);
	for(int i=1;i<=m;i++)
	{
		int in;
		cin>>in;
		if(in==1)
		{
			int x,y,k;
			cin>>x>>y>>k;
			upd(x,y,k,1);//区间修改 
		}
		else if(in==2)
		{
			int x,y;
			cin>>x>>y;
			cout<<sech(x,y,1)<<endl;//区间查询 
		}
	}
	return 0;
}

  以上就是关于带懒操作线段树区间查改的内容了。如果你觉得我讲得不错,就请点赞关注;如果我有什么疏漏,请在评论区留言,我会尽量即使改正/bx

 

标签:lazy,标记,int,线段,tree,num,查改,sum,节点
From: https://www.cnblogs.com/qj-Network-Box/p/17527155.html

相关文章

  • [Java]线段树
    线段树不含懒标记(单点修改)代码维护区间最大/最小值Node[]tr=newNode[400010];classNode{intl,r,max,min;Node(intl,intr,intmax,intmin){this.l=l;this.r=r;this.max=max;this.min=min;}}vo......
  • 2023ACM暑假训练day 8-9 线段树
    目录DAY8-9线段树训练情况简介题DAY8-9线段树训练地址:传送门训练情况简介题题意:思路:......
  • 用颜色标记法,实现树的前中后序遍历
    使用颜色标记法,实现树的前中后序遍历packagealgorithm;importjava.util.*;importjava.util.function.BiConsumer;/***树的前中后序遍历-颜色标记法*/publicclassTreeTraversal{//white-未访问过的节点privatestaticfinalintWHITE=0;/......
  • 深入学习 GC 算法 - 标记清除算法
    博主介绍:✌博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家✌......
  • 李超线段树 学习笔记
    李超线段树学习笔记今天模拟赛用到了李超线段树(但是本蒟蒻费了半天劲搞了个斜率优化拿到了60pts的好成绩/kk),所以学习一下李超线段树刻不容缓(学会了我貌似也切不来那道题qwq)。引入初中和高中我们都做过函数题吧,是不是有时候给你两根甚至几根直线,然后问你某个点的最值?当然,......
  • [数据结构]Segment tree(线段树)
    Segmenttree(线段树)1.线段树的结构和思想线段树基本结构:简单操作:1.单点修改:时间复杂度O(logn),因为线段树高是O(logn)的,然后会修改这个点到根的路径上的点权,所以是O(logn)的。2.区间查询(比如:最小值)实现:#include<bits/stdc++.h>usingnamespacestd;typedeflong......
  • Python.re正则表达式的标记
    标记方式在Python的re模块中,有以下几种标记(flags)可用于修改正则表达式的匹配行为:re.I(或re.IGNORECASE):忽略大小写匹配。例如,正则表达式[a-z]+将匹配小写字母字符串,而使用re.I标记后,它将匹配大小写混合或大写字母字符串。re.M(或re.MULTILINE):多行模式匹配。默认情况下,正......
  • 线段树优化建图 拓扑排序 6.22西安集训T1
    题目链接有一条无限长的数轴,上面有 nn 个坑,第 ii 个坑的位置为 x_ixi​。你将要在数轴上再放置 nn 个球,第 ii 个将要放到的位置为 y_iyi​。每当有一个球被放上去之后,它就会滚落到离它最近的一个坑里并填上那个坑。如果有两个坑都离它最近,那么它会落到左边的里面。现......
  • 「解题报告」P8861 线段
    有趣ds题。首先有一个部分分\(l_i\le10^5\ler_i\)。发现这相当于可以把区间分成左右两部分,那么我们可以考虑将左右分开考虑。我们将每个区间拆开成两部分,这样插入的时候就直接插入即可,修改操作时,发现实际上就是将左端所有长度大于\(10^5-l\)的区间长度改为\(10^5-......
  • 10000条“视频/音乐/书籍数据”命名实体识别标记数据分享
      类似于人名/地名/组织机构名的命名体识别数据集,资源标注了大约10000条视频/音乐/书籍数据。数据的意义希冀能够基于此训练NLP模型识别句子中的视频/音乐/书籍等名称信息.   数据的标注过程:  1、先纯手动提取标记了一部分(大约5000条),基于标注数据训练一个base模型,......