【算法每日一练]-结构优化(保姆级教程 篇4 树状数组,线段树,分块模板篇)

news2025/1/11 0:22:53

除了基础的前缀和,后面还有树状数组,线段树,分块的结构优化。

目录

分块

分块算法步骤:

树状数组

树状数组步骤:

线段树点更新

点更新步骤:

线段树区间更新

区间更新步骤:


分块

分块算法多用于求多次区间更新的区间和(在线算法)

题目:(POJ 3648) 一个简单的整数问题

        

分块算法思想:基于优化后的暴力。

分块的本质就是维护每个块的suf数组(和lz),然后分整个块处理和非整个块暴力处理!

        

分块算法步骤:

        

1,预处理块build:初始化每块的左右下标L[],和R[],每个下标的所属块号be,每块的和suf

        

2,区间修改update:对于完整的块仅修改懒标lz,不完整的就暴力修改a和suf

        

3,区间查询query :对于完整的块直接利用懒标lz和suf,不完整的就暴力a和lz

(这里没有什么懒标下放,这又不是求区间max)

#include <bits/stdc++.h>//POJ3648
using namespace std;
const int N=100010;
typedef long long ll;
ll suf[N],lz[N];//分块的本质是维护每个块的suf数组(和lz),然后分整个块处理和非整个块暴力处理(相当于优化后的暴力)
int a[N],L[N],R[N],be[N];
int n,m;
//分块:我们处理下标都是从1开始
void build(){//L[]R[]每块的左右下标,be[]每个下标的所属块号,suf[]每块的和
	int t=sqrt(n);
	int num=n/t;
	if(n%t) num++;//t是块长,num是块数,
	for(int i=1;i<=num;i++){
		L[i]=(i-1)*t+1;	R[i]=i*t;
	}
	R[num]=n;//更改最后一块的右下标
	for(int i=1;i<=num;i++)
		for(int j=L[i];j<=R[i];j++){
			be[j]=i;suf[i]+=a[j];//初始化每点的be和每块的suf
		}
}
//区间修改
void update (int l,int r,int d){//完整块就修改懒标lz,不完整就修改a,suf
	int p=be[l],q=be[r];
	if(p==q){//如果在同一块就暴力修改a和suf
		for(int i=l;i<=r;i++) a[i]+=d;
		suf[p]+=d*(r-l+1);
	}
	else{//否则:完整的块修改懒标lz,不完整还是暴力a和suf
		for(int i=p+1;i<=q-1;i++) lz[i]+=d;
		for(int i=l;i<=R[p];i++) a[i]+=d;
		suf[p]+=d*(R[p]-l+1);
		for(int i=L[q];i<=r;i++) a[i]+=d;
		suf[q]+=d*(r-L[q]+1);
	}
}

ll query(int l,int r){//完整块suf和lz,不完整就a和lz
	int p=be[l],q=be[r];ll ans=0;
	if(p==q){//同一块就看a和lz
		for(int i=l;i<=r;i++) ans+=a[i];
		ans+=lz[p]*(r-l+1);
	}
	else{//否则:完整就suf+lz,不完整还是a和lz
		for(int i=p+1;i<=q-1;i++) ans+=suf[i]+lz[i]*(R[i]-L[i]+1);
		for(int i=l;i<=R[p];i++) ans+=a[i];
		for(int i=L[q];i<=r;i++) ans+=a[i];
		ans+=lz[q]*(r-L[q]+1);
	}
	return ans;
}

int main(){
	cin>>n>>m;
	int l,r,d;char op[3];//不要输入字符,输入字符串
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	build();
	for(int i=1;i<=m;i++){
		scanf("%s %d %d",op,&l,&r);
		if(op[0]=='C'){
			scanf("%d",&d);
			update(l,r,d);
		}
		else{
			printf("%lld\n",query(l,r));
		}
	}
}

        

        

树状数组

树状数组多用于点更新的区间和(在线算法)

树状数组思想:和原数组一一对应,且是通过“二进制 ”分解维护区间

树状数组本质就是创建了一个离散的一维数组c,每个点维护不同区间的前缀和。修改add和查询sum都是离散进行的。性能是log(n)

                
c[i]区间维护长度: i末尾有连续的k个0,则c[i]保存的区间长度为2^k,即从a[i]向前的2^k个元素

         

树状数组步骤:

        
单点修改更新add:找后继  更新该元素所有的祖先节点

                
查询前缀和sum:找前驱    加上左侧所有子树的根(也就是自己的前兄弟,故称为前驱)

        

#include <bits/stdc++.h>//一维树状数组      性能是log(n)
using namespace std;
typedef long long ll;
const int maxn=10000;
ll c[maxn];//c[]为树状数组
int n,a[maxn];
int lowbit(int i){	return (-i)&i;}
//获取c[i]区间的长度(计算机中的负数是以补码来存的)
void add(int i,int z){	for(;i<=n;i+=lowbit(i)) c[i]+=z;}
//c[i]的后继都加上z:直接后继为[i+lowbit(i)]
ll sum(int i){//求前缀和a[1]~a[i],把i前面所有的根加上即可:直接前驱为[i-lowbit(i)]
	ll s=0;
	for(;i>0;i-=lowbit(i)) s+=c[i];
	return s;
}

ll sum(int i,int j){	return sum(j)-sum(i-1);}//求区间和

int main(){
	cin>>n;
	for(int i=1;i<=n;i++){//数组必须从1开始输入
		cin>>a[i];
		add(i,a[i]);//点更新,更新树状数组
	}	
	int x1,x2;
	cin>>x1;
	cout<<sum(x1)<<'\n';
	cin>>x1>>x2;
	cout<<sum(x1,x2);
	return 0;
}

那么仅仅把sum改成从(1,1)到(x,y)求和即可变成二维的树状数组

#include <bits/stdc++.h>//二维树状数组
using namespace std;
typedef long long ll;
const int maxn=10000;
ll c[maxn][maxn];
int n,a[maxn][maxn];

int lowbit(int i){	return (-i)&i;}

void add(int x,int y,int z){
	for(int i=x;i<=n;i+=lowbit(i))
	for(int j=y;j<=n;j+=lowbit(j)){
		c[i][j]+=z;
	}
}

ll sum(int x,int y){//求(1,1)到(x,y)和
	ll s=0;
	for(int i=x;i>0;i-=lowbit(i))
	for(int j=y;j>0;j-=lowbit(j)){
		s+=c[i][j];
	}
	return s;
}

ll sum(int x1,int y1,int x2,int y2){
	return sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1);
}

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++){
		cin>>a[i][j];
		add(i,j,a[i][j]);
	}
	int x1,y1,x2,y2;
	cin>>x1>>y1;
	cout<<sum(x1,y1)<<'\n';
	cin>>x1>>y1>>x2>>y2;
	cout<<sum(x1,y1,x2,y2);
	
}

        

        

线段树点更新

多用于求点更新的区间最值

线段树思想:建立一颗二叉树来用每个节点去维护对应区间的信息

线段树的本质是在4maxn大小的一维dfs序的树形离散数组tree(节点)上存储区间的信息。建立,更新和查询也都是离散的

        

要注意:

我们建立的二叉树在非叶子层时都是满二叉树。所以k节点的左孩子一定是2k,右孩子一定是2k+1另外k(节点)的值和l,r的值没有任何关系,只不过是l==r时候k是叶子节点

        

我们一定是按照dfs序来存储节点的,也因此叶子节点也是dfs序更新的

点更新步骤:

        

build:初始化节点信息:找到叶节点放置数组信息,然后上传到所有节点更新信息

        

update:找到对应的点将i下标的值更新为v

        

query: 找到对正确的节点就返回,否则就继续分叉找

        

千万别看代码多长,基本就函数中最前面的几句有用,剩余操作的都是在找孩子进行递归。

#include <bits/stdc++.h>
using namespace std;
const int maxn=100005,INF=0x3f3f3f3f;
int n,a[maxn];
struct node{  int l,r,mx;}tree[maxn*4];//存放左右端点l,r,mx为区间最值,tree存放树节点号

void build(int k,int l,int r){//创建线段树:初始化每个节点
	tree[k].l=l;tree[k].r=r;
	if(l==r){
		tree[k].mx=a[l];return ;
	}
	int mid=(l+r)/2;
	build(k<<1,l,mid);//建树时候范围一定要变化
	build(k<<1|1,mid+1,r);
	tree[k].mx=max(tree[k<<1].mx,tree[k<<1|1].mx);//创建完成孩子后再更新最大值
}

void update(int k,int i,int v){//单点修改:在k节点将i下标的值更新为v
	if(tree[k].l==tree[k].r&&tree[k].l==i){//找i下标的叶子更新
		tree[k].mx=v;return ;
	}
	int mid=(tree[k].l+tree[k].r)/2;
	if(i<=mid) update(k<<1,i,v);//否则就进入左子树lc或右子树更新rc
	else update(k<<1|1,i,v);
	tree[k].mx=max(tree[k<<1].mx,tree[k<<1|1].mx);//孩子更新完后修改最大值
}

int query(int k,int l,int r){//区间查询:找到对正确的节点就返回,否则就继续分叉找
	if(tree[k].l>=l&&tree[k].r<=r){
		return tree[k].mx;//找到了
	}
	int mid=(tree[k].l+tree[k].r)/2;
	int maxx=-INF;
	if(l<=mid){//否则就找左子树或右子树的最值
		maxx=max(maxx,query(k<<1,l,r));
	}
	if(r>mid){
		maxx=max(maxx,query(k<<1|1,l,r));
	}
	return maxx;
}

void print(int k){
	if(tree[k].mx){
		cout<<k<<"\t"<<tree[k].l<<"\t"<<tree[k].r<<"\t"<<tree[k].mx<<"\t"<<'\n';
		print(k<<1);
		print((k<<1)+1);
	}
}

int main(){
	int l,r,i,v;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,1,n);//创建二叉线段树,为啥传入树根呢?答:方便找左右孩子
	print(1);
	cin>>l>>r;
	cout<<query(1,l,r)<<'\n';//查询区间最值
	cin>>i>>v;
	update(1,i,v);//点更新
	print(1);
	cin>>l>>r;
	cout<<query(1,l,r)<<'\n';
}

输入样例后建立的二叉树: 

        

         

线段树区间更新

 多用于求区间更新的区间最值   

区间更新步骤:

        

创建线段树:初始化节点信息:找到叶节点放置数组信息,然后上传到所有节点更新信息

        

区间修改:在k节点上修改[l,r]区间为v值,整体包含就做懒标,否则就继续分叉(分叉前一定要懒标下移)

        

区间查询:找到对正确的节点就返回,否则就继续分叉找(分叉前一定要懒标下移)

也就是相对点更新来讲多了懒标的处理 

#include <bits/stdc++.h>
using namespace std;
const int maxn=100005,INF=0x3f3f3f3f;
int n,a[maxn];
struct node{  int l,r,mx,lz;}tree[maxn*4];//存放左右端点l,r,mx表示区间[l,r]最值,lz表示懒标 tree存放树节点号
//二叉树的本质是在4k的一维dfs序的树形离散数组(节点)上存储的信息。另外k(节点)的值和l,r的值没有任何关系,只不过是l==r时候k是叶子节点
void lazy(int k,int v){tree[k].mx=tree[k].lz=v;}//给节点k打懒标

void pushdown(int k){//从k节点下传懒标(传给子树),只会传一次,否则就退化成单点修改了
	lazy(k<<1,tree[k].lz);
	lazy(k<<1|1,tree[k].lz);//+的优先级太高了
	tree[k].lz=-1;//清除当前节点懒标
}

void build(int k,int l,int r){//创建线段树:创建二叉树,然后在叶节点放置数组信息,然后上传到所有节点更新信息
	tree[k].l=l;tree[k].r=r;tree[k].lz=-1;//初始化节点
	if(l==r){
		tree[k].mx=a[l];return ;//按dfs顺序更新叶子
	}
	int mid=(l+r)/2;
	build(k<<1,l,mid);//建树时候范围一定要变化
	build(k<<1|1,mid+1,r);//左右孩子节点为2k和2k+1,分别维护[l,mid]和[mid+1,r]的区间信息
	tree[k].mx=max(tree[k<<1].mx,tree[k<<1|1].mx);//更新最大值
}

void update(int k,int l,int r,int v){//区间修改:在k节点上修改[l,r]区间为v值,整体包含就做懒标,否则就继续分叉
	if(tree[k].l>=l&&tree[k].r<=r){//恰好找到区间(覆盖也行)
		return lazy(k,v);//直接做懒标记并结束本层,这个return不是返回值的
	}
	if(tree[k].lz!=-1) pushdown(k);//有懒标,先下移再进入子树(这样下个节点的lz和mx就更新了)
	int mid=(tree[k].l+tree[k].r)/2;
	if(l<=mid) update(k<<1,l,r,v);//传节点即可,因为我们用的是节点的信息(build时是l到mid区间为左子树,所以必须l<=mid)
	if(r>mid) update(k<<1|1,l,r,v);
	tree[k].mx=max(tree[k<<1].mx,tree[k<<1|1].mx);//更新最大值
}

int query(int k,int l,int r){//区间查询:找到对正确的节点就返回,否则就继续分叉找
	if(tree[k].l>=l&&tree[k].r<=r){//找到就返回
		return tree[k].mx;
	}
	if(tree[k].lz!=-1) pushdown(k);//有懒标,先下移再进入子树
	int mid =(tree[k].l+tree[k].r)/2;
	int maxx=-INF;
	if(l<=mid) maxx=max(maxx,query(k<<1,l,r));//否则就找左子树或右子树的最值
	if(r>mid)  maxx=max(maxx,query(k<<1|1,l,r));
	return maxx;
}

void print(int k){
	if(tree[k].mx){//从根开始dfs顺序访问每个节点(1,2,3 ……)
		cout<<k<<"\t"<<tree[k].l<<"\t"<<tree[k].r<<"\t"<<tree[k].mx<<"\t"<<'\n';
		print(k<<1);
		print(k<<1|1);
	}
}

int  main(){
	int l,r,v;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,1,n);//创建线段树 
	print(1);
	cin>>l>>r;
	cout<<query(1,l,r)<<'\n';//区间查询
	cin>>l>>r>>v;
	update(1,l,r,v);//区间修改
	print(1);
	for(int i=1;i<=n;i++)cout<<a[i]<<' ';
	cout<<"\n";
	while(l){
	cin>>l>>r;
	cout<<query(1,l,r)<<'\n';
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1292399.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux CentOS本地部署SQL Server数据库结合cpolar内网穿透实现公网访问

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、Cpolar杂谈 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 安装sql server二. 局域网测试连接三. 安装cpolar内网穿透四. 将sqlserver映射…

集合进阶指南:从基础知识到高级应用

集合高级 Collection集合 数组和集合的区别 相同点 都是容器,可以存储多个数据 不同点 数组的长度是不可变的,集合的长度是可变的数组可以存储基本数据类型和引用数据类型 集合只能存储引用数据类型,如果要存基本数据类型,需要存对应的包装类 集合类体系结构 Collectio…

一天一个设计模式---原型模式

基本概念 原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;其主要目的是通过复制现有对象来创建新对象&#xff0c;而不是通过实例化类。原型模式允许在运行时动态创建对象&#xff0c;同时避免了耦合与子类化。 在原型模式中&#xff0…

Tap虚拟网卡

1 概述 Tap设备通常用于虚拟化场景下&#xff0c;其驱动代码位于drivers/net/tun.c&#xff0c;tap与tun复用大部分代码&#xff0c; 注&#xff1a;drivers/net/tap.c并不是tap设备的代码&#xff0c;而是macvtap和ipvtap&#xff1b; 下文中&#xff0c;我们统一称tap&#…

C++ vector基本操作

目录 一、介绍 二、定义 三、迭代器 四、容量操作 1、size 2、capacity 3、empty 4、resize 5、reserve 总结&#xff08;扩容机制&#xff09; 五、增删查改 1、push_back & pop_back 2、find 3、insert 4、erase 5、swap 6、operator[] 一、介绍 vector…

圣诞将至—C语言圣诞树代码来啦

文章目录 圣诞将至—C实现语言圣诞树源码 圣诞将至—C实现语言圣诞树 圣诞树 源码 #define _CRT_SECURE_NO_WARNINGS#include <stdio.h> #include <math.h> #include <stdlib.h> #include <windows.h> #include <time.h> #define PI 3.14159265…

Maven-高效的Java项目构建与管理工具(含Maven详细安装与配置过程)

Maven 什么是Maven&#xff1f; 正如题目所说&#xff0c;Maven就是一款高效的Java项目构建与管理工具&#xff0c;基于项目对象模型&#xff08;POM&#xff09;概念&#xff0c;利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。是Apache软件基金会的一个开源…

全球市场调研:找准热门产品,开创跨境电商新蓝海

在全球数字化浪潮的推动下&#xff0c;跨境电商正蓬勃发展&#xff0c;成为连接世界各地消费者与商品的桥梁。然而&#xff0c;在竞争激烈的市场中要想脱颖而出&#xff0c;关键在于深入的全球市场调研。本文将探讨如何通过全球市场调研找准热门产品&#xff0c;开创跨境电商的…

如何使用Matlab完成窗口与子窗口

目录 一、前言 二、主窗口与主窗口按钮 三、子窗口 四、调用函数并显示在子窗口中的文本框中 五、关闭子窗口 一、前言 有时候需要借用Matlab完成一个图窗功能&#xff0c;但是我们的程序不仅拥有功能&#xff0c;还拥有一些子功能&#xff0c;那么我们该如何借助Matlab完…

基于OpenCV+CNN+IOT+微信小程序智能果实采摘指导系统——深度学习算法应用(含pytho、JS工程源码)+数据集+模型(四)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境TensorFlow 环境Jupyter Notebook环境Pycharm 环境微信开发者工具OneNET云平台 模块实现1. 数据预处理2. 创建模型并编译3. 模型训练及保存1&#xff09;模型训练2&#xff09;模型保存 4. 上传结果1&#xff09;…

[TKDE2020]@Multi-Source_Spatial_Entity_Linkage

论文地址&#xff1a;https://arxiv.org/pdf/1911.09016v1.pdf&#xff08;下文中提及的引用信息如未解释&#xff0c;请索引原论文末的参考文献&#xff09; 论文中提到的SSTD2019Multi-Source Spatial Entity Linkage (提取码&#xff1a;i3xt) 论文重要部分翻译 Abstract …

LeedCode刷题---双指针问题(二)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C/C》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、盛水最多的容器 题目链接&#xff1a;盛最多水的容器 题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xf…

C#大型LIS检验信息系统项目源码

LIS系统&#xff0c;一套医院检验科信息系统。它是以数据库为核心&#xff0c;将实验仪器与电脑连接成网&#xff0c;基础功能包括病人样本登录、实验数据存取、报告审核、打印分发等。除基础功能外&#xff0c;实验数据统计分析、质量控制管理、人员权限管理、试剂出入库等功能…

vscode eide arm-gcc 编译环境搭建调试

安装cube&#xff0c;vscode 1.安装vscode插件 C/C Extension Pack Chinese (Simplified) (简体中文) Language Pack Cortex-Debug Embedded IDE 工具链设置 2.软件工程生成 调试 3.生成工程&#xff0c;导入工程 4. 配置工程 编译完毕

【Git】本地代码如何托管到远程仓库(保姆级教程)

注意前提是你本地已经安装了Git 1. 新建远程仓库 选一个git服务器&#xff0c;新建一个远程仓库&#xff0c;这里我选用的是gitee 点击确定后&#xff0c;显示如下界面表示仓库已经新建完成 2.建立本地仓库 本地新建一个空文件夹&#xff0c;在里面写一些你的初始代码文件。…

Java / Scala - Trie 树简介与应用实现

目录 一.引言 二.Tire 树简介 1.树 Tree 2.二叉搜索树 Binary Search Tree 3.字典树 Trie Tree 3.1 基本概念 3.2 额外信息 3.3 结点实现 3.4 查找与存储 三.Trie 树应用 1.应用场景 2.Java / Scala 实现 2.1 Pom 依赖 2.2 关键词匹配 四.总结 一.引言 Trie 树…

C++初阶(十四)list

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、 list的介绍二、list的模拟实现1、list的节点2、list 的迭代器3、list4、打印5、完整代码…

Ubuntu宝塔面板本地部署Emlog个人博客网站并远程访问【内网穿透】

文章目录 前言1. 网站搭建1.1 Emolog网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试总结 前言 博客作为使…

银河麒麟安装lnmp,安装chrome。

安装lnmp 下载相关文件 链接&#xff1a;https://pan.baidu.com/s/1YqFLfGpE5DP3Sf_2GsXqNg?pwdptsn –来自百度网盘超级会员V7的分享 上传到服务器 我所选择上传的地方是 /home/npf/nginx-server&#xff0c; php放在跟nginx-server的同级目录 cd / mkdir home /home/npf…

系列学习前端之第 1 章:安装开发工具 VSCode

1、下载 官网下载地址&#xff1a;https://code.visualstudio.com/Download 根据自己电脑的操作系统下载即可 2、安装 正常的软件安装即可 3、下载中文插件&#xff08;汉化&#xff09; 点击左下角齿轮状的图标&#xff0c;选择【Extensions】&#xff0c;在搜索框输入【…