首页 > 其他分享 >线段树学习笔记(更新中)

线段树学习笔记(更新中)

时间:2024-03-10 23:44:32浏览次数:24  
标签:rs int 线段 mid 更新 笔记 区间 lz sum

本文章开始写作于2024年3月10日22:48,这也是我第一次没有参考板子,独立写出一个线段树的时刻(虽然只是板子题并且debug的时候还是参考了一下)

写这个主要是为了我自己以后复习起来方便,毕竟这玩意还是极其容易写挂的

注意:以下内容中标为斜体的是需要按照题目要求具体情况具体分析的,文章中的操作和代码是对板子题而言的

下面是板子题的完整代码(区间加法,区间求和)

#include<bits/stdc++.h>
#define int long long
#define ls (p << 1)
#define rs ((p << 1) | 1)
#define mid (s + ((t - s) >> 1))
using namespace std;
const int MX = 4000055;
int a[MX],sum[MX],lz[MX];
void build(int s,int t,int p) {
	if(s == t) {
		sum[p] = a[s];
		return;
	}
	build(s,mid,ls);
	build(mid + 1,t,rs);
	sum[p] = sum[ls] + sum[rs];
}
void pushdown(int p,int s,int t) {
	if(lz[p]) {
		sum[ls] += lz[p] * (mid - s + 1);
		sum[rs] += lz[p] * (t - mid);
		lz[ls] += lz[p];
		lz[rs] += lz[p];
		lz[p] = 0;
	}
}
void update(int l,int r,int s,int t,int p,int v) {
	if(s >= l && t <= r) {
		sum[p] += v * (t - s + 1);
		lz[p] += v;
		return; 
	}
	pushdown(p,s,t);
	if(l <= mid) update(l,r,s,mid,ls,v);
	if(r >= mid + 1) update(l,r,mid + 1,t,rs,v);
	sum[p] = sum[ls] + sum[rs]; 
}
int query(int l,int r,int s,int t,int p) {
	if(s >= l && t <= r) return sum[p];
	pushdown(p,s,t);
	int ans = 0;
	if(l <= mid) ans += query(l,r,s,mid,ls);
	if(r >= mid + 1) ans += query(l,r,mid + 1,t,rs);
	return ans;
} 
signed main()
{
	int op,x,y,k,n,q;
	scanf("%lld%lld",&n,&q);
	for(int i = 1;i <= n;i++) scanf("%lld",&a[i]);
	build(1,n,1);
	for(int i = 1;i <= q;i++) {
		scanf("%lld",&op);
		if(op == 1) {
			scanf("%lld%lld%lld",&x,&y,&k);
			update(x,y,1,n,1,k);
		}
		else {
			scanf("%lld%lld",&x,&y);
			printf("%lld\n",query(x,y,1,n,1));
		}
	}
	return 0;
}

我们接下来慢慢分析(勿喷

#define ls (p << 1)
#define rs ((p << 1) | 1)
#define mid (s + ((t - s) >> 1))

一大堆莫名其妙的define。
把左儿子编号、右儿子编号和中间位置缩略了一下。

以下的代码中,

a[]代表原数列,sum[]代表区间的和,lz[]代表懒标记,
l、r代表目标区间的左右端点,s、t代表当前操作区间的左右端点,p代表当前操作区间的编号。


void build(int s,int t,int p) {
	if(s == t) {
		sum[p] = a[s];
		return;
	}
	build(s,mid,ls);
	build(mid + 1,t,rs);
	sum[p] = sum[ls] + sum[rs];
}

这段代码是用来建树的,首先,如果当前区间的左右端点相等(也就是只有一个点), 那就把它的sum值赋值为原数列中对应位置的数(一个数的和不就是他自己吗)

否则,就不断对左右子区间递归操作,直到区间只有一个数。

最后,不要忘了将每个区间的sum值赋值为它左右两个子区间sum值的和。


void pushdown(int p,int s,int t) {
	if(lz[p]) {
		sum[ls] += lz[p] * (mid - s + 1);
		sum[rs] += lz[p] * (t - mid);
		lz[ls] += lz[p];
		lz[rs] += lz[p];
		lz[p] = 0;
	}
}

这段代码是用来下放懒标记的。

首先解释一下懒标记的作用:

如果某次要求对一个大区间进行修改,那么正常来说每次修改的时候都要对它的子区间进行更新以保证正确性+,但是这样的时间复杂度无法接受,是\(O(N)\)的、

那么如何优化呢?
那就是,当每次修改大区间的时候只对大区间的维护值进行更新,而其子区间的维护值等到需要查询或修改的时候再更新。
这样做的话可以优化掉大量不必要的操作,从而降低时间复杂度,使得线段树的时间复杂度为\(O(nlogn)\)的。
这就很nice。

sum[ls] += lz[p] * (mid - s + 1);
sum[rs] += lz[p] * (t - mid);

这两句的作用是用父区间的懒标记更新左右子区间的维护值。

lz[ls] += lz[p];
lz[rs] += lz[p];
lz[p] = 0;

这三句的作用是更新左右子区间的懒标记(因为只更新了“儿子”区间的维护值,“孙子”区间的维护值还没更新呢,需要将父区间的懒标记下传给“儿子”区间以正确更新“孙子”区间)并清空父区间的懒标记。


void update(int l,int r,int s,int t,int p,int v) {
	if(s >= l && t <= r) {
		sum[p] += v * (t - s + 1);
		lz[p] += v;
		return; 
	}
	pushdown(p,s,t);
	if(l <= mid) update(l,r,s,mid,ls,v);
	if(r >= mid + 1) update(l,r,mid + 1,t,rs,v);
	sum[p] = sum[ls] + sum[rs]; 
}

这段代码的作用是更新区间。
如果目标区间完全覆盖了当前操作区间,那么直接更新当前操作区间的维护值。 更新方法具体情况具体分析,比如板子题要求区间加法,给区间内的每个数都加上一个值,那么区间和的更新量就是区间元素数量乘以要加的值。
如果没有完全覆盖,就先下放当前区间的懒标记以保证正确性(否则子区间的更新量可能会是错误的),然后递归更新左右子区间。

最后,将父区间的维护值更新为左右子区间的和。注意这里要写“=”而不是“+=”!


 int query(int l,int r,int s,int t,int p) {
	if(s >= l && t <= r) return sum[p];
	pushdown(p,s,t);
	int ans = 0;
	if(l <= mid) ans += query(l,r,s,mid,ls);
	if(r >= mid + 1) ans += query(l,r,mid + 1,t,rs);
	return ans;
} 

这段代码是用来对区间求和的。
同样,如果目标区间覆盖了当前操作区间,直接返回当前操作区间的维护值;否则,先下放懒标记,然后递归处理左右子区间,最后返回答案。


以上就是线段树的基本框架(以板子题为例),本文持续更新中。

标签:rs,int,线段,mid,更新,笔记,区间,lz,sum
From: https://www.cnblogs.com/viki617/p/18065109

相关文章

  • Typescript学习笔记(一)
    学习日期:03-09-2024关键字:Typescript;安装;原始数据类型;Any类型;数组;元组;Typescript是Javascript的超集,显著区别是加了静态类型风格的类型系统、es6-es10-esnext的语法支持安装npminstall-gtypescript原始数据类型Boolean、Null、Undefined、Number、BigInt、String、Sy......
  • C#的笔记~TWO
    1、标识符命名的两个注意事项(1)标识符不能与C#关键字冲突(2)标识符区分大小写intage=30;intAge=30;【这两个符合规则】2、两种标识符命名方法:(1)Pascal命名法:所有单词第一个字母大写,其它字母小写。eg.UserGetinfo(2)Camel命名法:除了第一个单词,所有单词第一个字母......
  • 大型数据库应用——一些笔记
    这学期选了大型数据库应用,主要是和java一起用的,然后这里是一些笔记,可能会加上之前的一些笔记,之前学过数据库原理。一、介绍一些数据库1数据库分类数据库根据数据结构可分为关系型数据库和非关系型数据库。非关系型数据库中根据应用场景又可分为键......
  • MYSQL学习笔记23: 多表查询(自连接内连接+左右外连接)
    多表查询(自连接)自连接查询,可以是内连接查询,也可以是外连接查询select字段列表from表A别名Ajoin表A别名Bon条件...;自连接内连接查询员工以及所属领导的名字#可以这样写selecte1.name'员工',e2.name'上司'fromempe1joinempe2one1.man......
  • MYSQL学习笔记24: 多表查询(联合查询,Union, Union All)
    多表查询(联合查询,union,unionall)union查询需要多张表的列数一致,字段类型也保持一致对于union查询,就是把多次查询的结果合并起来,形成一个新的查询结果集select字段列表from表A...union[all]select字段列表from表B...;查询出薪资低于10000,或年龄......
  • MYSQL学习笔记25: 多表查询(子查询)[标量子查询,列子查询]
    多表查询(子查询)子查询,也称嵌套查询子查询的语句可以是insert/update/delete/select中的任何一个根据子查询的结果不同,可以分为:标量子查询(结果为单个值)列子查询(结果为一列)行子查询(子查询结果为一行)表子查询(子查询结果为多行多列)select*fromt1wh......
  • 03/10/2024 上课笔记 & 解题报告
    双向链表前言第一次接触这玩意儿,所以记录一下。题目[国家集训队]种树题目描述A城市有一个巨大的圆形广场,为了绿化环境和净化空气,市政府决定沿圆形广场外圈种一圈树。园林部门得到指令后,初步规划出\(n\)个种树的位置,顺时针编号\(1\)到\(n\)。并且每个位置都有一个美观......
  • 当年明月《明朝那些事儿》读书笔记(一)
    2024年新开了这套《明朝那些事儿》(作者:当年明月),一开始便停不下来,从1月读到3月,目前已经把前两部基本读完了。读完也是颇为感慨,随笔写点收获/感想吧。回忆过去,中学时期,自己对历史学科不能说心生厌恶,但也的确提不起兴趣,于是一直仅是在应试,而实际知之甚少。所以感谢这套书,让我能了解......
  • PARA笔记系统:简单高效管理个人信息及资料
    内容简介:在学习、工作中,会积累越来越多的资料。资料一多,会导致混乱。有时找个资料,需要花半天时间。这套简单、高效的笔记管理方法——PARA,把所有的事情,分成简单的4类。这套系统已经流行了十余年,被很多人验证有效。笔者自己也用了有1年多,感觉很有效。作者是TiagoForte,个人知......
  • Go语言精进之路读书笔记第44条——正确运用fake、stub和mock等辅助单元测试
    44.1fake:真实组件或服务的简化实现版替身fake测试就是指采用真实组件或服务的简化版实现作为替身,以满足被测代码的外部依赖需求。使用fake替身进行测试的最常见理由是在测试环境无法构造被测代码所依赖的外部组件或服务,或者这些组件/服务有副作用。typefakeOkMailerstruct......