洛谷P3372 【模板】线段树 1以及分块

news/2025/2/2 3:49:33 标签: 算法, c++, 分块, 暴力, 数据结构

【模板】线段树 1

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 k k k
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 n , m n, m n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。

接下来 m m m 行每行包含 3 3 3 4 4 4 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 [ x , y ] [x, y] [x,y] 内每个数加上 k k k
  2. 2 x y:输出区间 [ x , y ] [x, y] [x,y] 内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

样例 #1

样例输入 #1

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

样例输出 #1

11
8
20

提示

对于 30 % 30\% 30% 的数据: n ≤ 8 n \le 8 n8 m ≤ 10 m \le 10 m10
对于 70 % 70\% 70% 的数据: n ≤ 10 3 n \le {10}^3 n103 m ≤ 10 4 m \le {10}^4 m104
对于 100 % 100\% 100% 的数据: 1 ≤ n , m ≤ 10 5 1 \le n, m \le {10}^5 1n,m105

保证任意时刻数列中所有元素的绝对值之和 ≤ 10 18 \le {10}^{18} 1018

【样例解释】

#

思路

先不要看算法标签和题目名称。
通过观察可以发现,如果强行枚举,绝对会超时(不会有玄学方法能过吧
看题目的数据范围, 1 ≤ n , m ≤ 10 5 1 \le n, m \le {10}^5 1n,m105
那么这道题目时间复杂度的最高 O ( n n ) O(n\sqrt n) O(nn )

这道题目明显做法很多,线段树、树状数组可以用十分优秀的 O ( n log ⁡ n ) O(n\log n) O(nlogn)的时间复杂度通过。
但是明显线段树、树状数组这些做法太长了不好写
因此,分块成了一种简单好写,而且时间复杂度较为优秀的思想。
分块的时间复杂度略高于线段树、树状数组,为 O ( n n ) O(n\sqrt n) O(nn ),所以分块可以在这一题替代线段树。

分块原理

先考虑暴力,最简单的思路就是每一次操作进行增加就从 l l l枚举到 r r r,增加每一个数,求和就从 l l l枚举到 r r r,进行累加。(这里的 l l l r r r代表题目中的 x x x y y y
显然时间大大滴超。
导致超时的情况是 l l l r r r的差值很大,接下来考虑如何优化掉这种情况。
可以将数组分成很多
l l l r r r的差值很大的情况下,这个区间将会覆盖很多整块。
比如 n = 1000 n=1000 n=1000的情况下,每个块的大小为 100 100 100时,假如 l = 10 l=10 l=10 r = 500 r=500 r=500时,这个区间就可以看作4个完整块以及一个不完整的块,如果可以在 O ( 1 ) O(1) O(1)的时间将每个完整块处理,那么这次操作的时间复杂度就会降到 O ( n ÷ 10 ) O(n÷10) O(n÷10)左右。

分块的块的大小

在面对并不在一个完整的块内的情况,只能进行暴力求解,设块的大小为 s i z siz siz,则时间复杂度为 O ( s i z ) O(siz) O(siz)
覆盖多个完整块的情况下,每个完整块都是 O ( 1 ) O(1) O(1),设共有 n u m num num块,则时间复杂度为 O ( n u m ) O(num) O(num)
因为块数=总数÷块的大小,因此 n u m = n / s i z , n u m ∗ s i z = n num=n/siz,num*siz=n num=n/siznumsiz=n
为了让时间复杂度尽可能低,要 n u m num num s i z siz siz尽可能都小,此时最好的办法就是将 s i z siz siz n u m num num设为 n \sqrt n n
因此块的大小最好为 n \sqrt n n
此时的时间复杂度为 m n m\sqrt n mn 其中 m m m为操作次数, n n n为元素个数。

这道题的分块实现

所需的数组和变量

ll siz,num;//块个数和块大小
ll L[N],R[N];//每个块的左右端点
ll bel[N],su[N],add[N];//每个元素属于哪一个块,以及每个块的和、每个块每个元素都加上多少
ll n,m,a[N];//对应输入

分块初始化

void init(){
	siz=sqrt(n);//最佳的分块大小
	num=n/siz+(n%siz>0);//块的个数
	for(int i=1;i<=num;i++){//这里预处理,其实也可以封装函数后面计算
		L[i]=siz*(i-1)+1;
		R[i]=min(n,siz*i);
	}
	for(int i=1;i<=n;i++){//这里也是预处理
		bel[i]=(i-1)/siz+1;
		su[bel[i]]+=a[i];
	}
}

预处理可以降低常数

区间增加某个数

void upd(ll l,ll r,ll k){
	ll x=bel[l],y=bel[r];
	if(x==y){//在同一块内的情况
		for(int i=l;i<=r;i++){
			a[i]+=k;
			su[x]+=k;
		}
	}
	else{
		for(int i=x+1;i<y;i++){//整块
			add[i]+=k;
			su[i]+=k*siz;
		}
		for(int i=l;i<=R[x];i++){//非完整
			a[i]+=k;
			su[x]+=k;
		}
		for(int i=L[y];i<=r;i++){//非完整
			a[i]+=k;
			su[y]+=k;
		}
	}
}

这里可以看作每一个块内所有元素都加上的某个数暂时没有加上去,而是保留到需要用的时候再进行处理。
面对不完整的块直接暴力就行。

区间求和

ll qu(ll l,ll r){
	ll ans=0;//记录答案
	ll x=bel[l],y=bel[r];
	if(x==y){//同一个块内直接暴力就行
		for(int i=l;i<=r;i++){
			ans+=a[i]+add[x];//加上add是因为把之前没有加上的加上
		}
	}
	else{
		for(int i=x+1;i<y;i++){
			ans+=su[i];//每个块的和之前记录了
		}
		for(int i=l;i<=R[x];i++){
			ans+=a[i]+add[x];//这里也是一样
		}
		for(int i=L[y];i<=r;i++){
			ans+=a[i]+add[y];//同上
		}
	}
	return ans;
}

查找时,之前没有加上去放在add数组里的值就可以加上了,优化了时间复杂度。
可以看出分块还是一种暴力
这里也可以看出预处理的作用。

“完整”代码

直接复制代码不是一个好习惯。

ll siz,num,L[N],R[N],bel[N],su[N],add[N];
ll n,m,a[N];
void init(){
	siz=sqrt(n);
	num=n/siz+(n%siz>0);
	for(int i=1;i<=num;i++){
		L[i]=siz*(i-1)+1;
		R[i]=min(n,siz*i);
	}
	for(int i=1;i<=n;i++){
		bel[i]=(i-1)/siz+1;
		su[bel[i]]+=a[i];
	}
}
void upd(ll l,ll r,ll k){
	ll x=bel[l],y=bel[r];
	if(x==y){
		for(int i=l;i<=r;i++){
			a[i]+=k;
			su[x]+=k;
		}
	}
	else{
		for(int i=x+1;i<y;i++){
			add[i]+=k;
			su[i]+=k*siz;
		}
		for(int i=l;i<=R[x];i++){
			a[i]+=k;
			su[x]+=k;
		}
		for(int i=L[y];i<=r;i++){
			a[i]+=k;
			su[y]+=k;
		}
	}
}
ll qu(ll l,ll r){
	ll ans=0;
	ll x=bel[l],y=bel[r];
	if(x==y){
		for(int i=l;i<=r;i++){
			ans+=a[i]+add[x];
		}
	}
	else{
		for(int i=x+1;i<y;i++){
			ans+=su[i];
		}
		for(int i=l;i<=R[x];i++){
			ans+=a[i]+add[x];
		}
		for(int i=L[y];i<=r;i++){
			ans+=a[i]+add[y];
		}
	}
	return ans;
}
int main(){
	scanf("%lld %lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	init();
	for(int i=1;i<=m;i++){
		int op;
		scanf("%d",&op);
		if(op==1){
			ll x,y,k;
			scanf("%lld %lld %lld",&x,&y,&k);
			upd(x,y,k);
		}
		else{
			ll x,y;
			scanf("%lld %lld",&x,&y);
			ll tmp=qu(x,y);
			printf("%lld\n",tmp);
		}
	}
	return 0;
}

分块和线段树对比的优势和劣势

时间复杂度上

线段树的时间复杂度平均 O ( n log ⁡ n ) O(n\log n) O(nlogn)
分块时间复杂度 O ( n n ) O(n\sqrt n) O(nn )
分块时间复杂度较高

空间复杂度上

线段树和分块的空间复杂度均为 O ( n ) O(n) O(n),but实际上分块的空间更小,不容易被卡。

代码复杂度上

d a l a o dalao dalao们的线段树代码100行左右。
分块80行左右,代码量要少。

线段树的思路比较难理解。
分块的思路就是暴力,十分容易理解,分块更好。

结果

可以看出分块与线段树不相上下,面对大部分区间问题分块足以。不行就用莫队


http://www.niftyadmin.cn/n/5839725.html

相关文章

分享| RL-GPT 框架通过慢agent和快agent结合提高AI解决复杂任务的能力-Arxiv

结论 “RL-GPT: Integrating Reinforcement Learning and Code-as-policy” RL-GPT 框架为解决大语言模型在复杂任务处理中的难题提供了创新有效的途径&#xff0c; 旨在将强化学习&#xff08;RL&#xff09;和代码即策略相结合&#xff0c; 以解决大语言模型&#xff08…

C#,入门教程(08)——基本数据类型及使用的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(07)——软件项目的源文件与目录结构https://blog.csdn.net/beijinghorn/article/details/124139947 数据类型用于指定数据体&#xff08;DataEntity&#xff0c;包括但不限于类或结构体的属性、变量、常量、函数返回值&#xff09;…

KineStop:手机上的智能防晕车助手

KineStop是一款专为晕车用户设计的智能防晕车应用&#xff0c;通过手机传感器精准识别车辆运动状态&#xff0c;并在屏幕上实时提示用户&#xff0c;帮助缓解晕车不适。它无需复杂设置&#xff0c;仅需Android 7.0及以上系统&#xff0c;即可实现“即开即用”&#xff0c;随时随…

[原创](Modern C++)现代C++的关键性概念: 正则表达式

常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delphi、XCode、Eclipse、C Bui…

CentOs9新手教程

CentOS 9是基于RHEL的CentOS Stream版本&#xff0c;主要用于开发和测试环境&#xff0c;不适合作为生产环境的稳定系统。它提供了最新的软件和功能&#xff0c;但可能存在不稳定性和兼容性问题。如果你需要一个稳定的生产环境&#xff0c;建议使用CentOS Linux版本。 安装环境…

三次方根pow

给定一个浮点数n&#xff0c;求它的三次方根。 输入格式: 共一行&#xff0c;包含一个浮点数n,−10000≤n≤10000。 输出格式: 共一行&#xff0c;包含一个浮点数&#xff0c;表示问题的解。 注意&#xff0c;结果保留6位小数。 输入样例: 1000.00输出样例: 10.000000 …

实测数据处理(Wk算法处理)——SAR成像算法系列(十二)

系列文章目录 《SAR学习笔记-SAR成像算法系列&#xff08;一&#xff09;》 《wk算法-SAR成像算法系列&#xff08;五&#xff09;》 文章目录 前言 一、算法流程 1.1、回波信号生成 2.2 Stolt插值 2.3 距离脉冲压缩 2.4 方位脉冲压缩 2.5 SAR成像 二、仿真实验 2.1、仿真参数…

精品PPT | 华为企业数据架构、应用架构及技术架构设计方法

这份PPT详细介绍了华为企业数据架构、应用架构及技术架构的设计方法。它涵盖了数据架构的五大原则&#xff0c;包括数据按对象管理、企业全局视角定义数据架构、遵从企业数据分类管理框架、概念实体结构化数字化以及数据服务化同源共享等&#xff0c;旨在确保数据在企业内的一致…