树状数组 Color the ball hdu 1556 线段树 洛谷p3372

目录

前言

树状数组

   lowbit函数

   直观表述 

      代码

      运行结果

树状数组构建代码

树状数组的应用

   单点修改和(单点)区间查询

   结合差分数组区间修改 ,单点查询

        差分数组

Color the ball hdu 1556

   问题描述

   问题分析

   代码

线段树 洛谷p3372

   问题描述

   问题分析        

   代码


前言

        树状数组和并查集一样,也是一个高级数据结构,编码简单,在算法竞赛中有着极其强大的应用。

  • 并查集主要解决的是集合的问题,而树状数组主要解决的则是有关区间的问题;
  • 并查集最本质的优化就是可以迅速找到元素所属的集合,树状数组则是可以在动态的数组中快速查询区间和。

树状数组

        树状数组,顾名思义,就是一个长得“很像”树的数组,本质上还是一个数组,但这个数组并不是常规的索引数组

   lowbit函数

        这个函数的功能是找到x的二进制数的最后一个1,换成十进制就表示能整除x的最大2的次幂

#define lowbit(x) ((x)&-(x))

   直观表述 

   下面我们来编写一个程序,直观表示下树状数组对应于数组的表示的元素

      代码

#include<iostream>
using namespace std;
const int N = 100005;

#define lowbit(x) ((x)&-(x))

int tree[N];

int main() {
	int n;
	cout << "输入树状数组的元素个数:";
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int a = lowbit(i);
		cout << "tree[" << i << "] = ";
		int k = i;
		while(a){
			if (a == 1) {
				cout << "a[" << k << "]";
			}
			else cout << "a[" << k << "] + ";
			k--;
			a--;
		}
		cout << endl;
	}
}

      运行结果

        那么我们根据这个结果试着构建一棵线段树:

树状数组构建代码

#define lowbit(x) ((x)&-(x))

int segment_tree[N + 1] = {0};  //注意tree【0】不使用

void update(int x, int d) {
	while (x <= N) {
		segment_tree[x] += d;  //更新其所属的根节点
		x += lowbit(x);
	}
}

int sum(int x) {
	int ans = 0;
	while (x > 0) {
		ans += segment_tree[x];
		x -= lowbit(x);
	}
	return ans;
}

树状数组的应用

        树状数组的操作都是直接作用在数组上的,树状数组只是辅助作用

   单点修改和(单点)区间查询

    

        根据这个树状数组,假设我们要更新a[2],那么相应的就需要更新tree[2],tree[4],tree[8]

会发现:

  • 2+lowbit(2)=4
  • 4+lowbit(4)=8

那么我们就可以得出单点修改的代码:

void update(int x, int d) {
	while (x <= N) {
		segment_tree[x] += d;  
		x += lowbit(x);
	}
}

        树状数组区间查询是利用前缀和实现的,如果要算sum(7),那么根据树状数组sum(7)=tree[7] + tree[6] + tree[4];会发现

  • 7-lowbit(7)=6
  • 6-lowbit(6)=4
  • 4-lowbit(4)=0

那么我们就可以得到前缀和的算法:

int sum(int x) {
	int ans = 0;
	while (x > 0) {
		ans += segment_tree[x];
		x -= lowbit(x);
	}
	return ans;
}

        根据前缀和我们就可以计算任意区间【L,R】的区间和:sum(L) - sum(R-1) ,实现单点查询m:sum(m) - sum(m-1)

   结合差分数组区间修改 ,单点查询

        差分数组

        会发现使用树状数组时如果要进行区间修改是一件非常耗时的事情,应为树状数组的单点修改不仅仅要修改单个点,还要修改与该点相关的所有父节点,但差分数组可以快速实现区间修改,如果不是在线操作,那么将不需要将区间中所有元素修改个遍,所以将树状数组和差分数组结合可以极大提高在动态数组中区间修改的效率。

        树状数组结合差分数组时,我们将a[]数组当成差分数组,tree[]数组仍然是辅助,那么在进行单点查询的时候只需要调用sum(i)函数即可知道i节点的修改信息。 这里我们借助一道例题理解。


Color the ball hdu 1556

hdu 1556

   问题描述

        有几个气球排成一排,每次选择一个区间对这个区间内的气球涂色,经过几次涂色后问这些气球被涂色的次数。

   问题分析

        问题分析之前先介绍下先介绍下算法编程中的两种情况“离线”情况,“强制在线”情况

  • 离线:是非交互的,一次读取很多信息,然后在对这些信息整合
  • 强制在线:是交互的,要求一问一答        

         很明显,本题是一个离线情况,有多次操作,但只在最后查询,如果是离线操作那么就可以使用差分数组,这是一个常见的离线型数据结构(这个没必要特别记忆,练多了也就没有离线和在线之分了),因为差分数组可以记录修改信息,然后根据前缀和操作处理这些信息。

        所以本题可以定义一个差分数组,先记录被涂色信息,然后在最后应用前缀和算法计算涂色信息。但本题其实可以利用树状数组优化,前面说过了,树状数组最大的优势就是可以迅速得到前缀和,

        细心的朋友可能注意到,树状数组的修改操作不仅仅需要修改一头一尾,还要修改与头尾有关的根结点,但是如果只用差分数组的话,修改操作只需要修改区间的一头一尾即可,确实这样来看修改操作好像并没有优化,反而更加复杂,但这些都是值得的,因为如果是差分数组求前缀和是需要一个一个加的,但树状数组由于前面的准备操作,使得求前缀和操作不需要一项一项加,而且修改操作是一劳永逸的,因为在进行前缀和操作时,会发现会不止一次用到修改操作带来的数据,所以一切都是值得的,而且树状数组+差分需要的内存和只用差分所需内存一样多。

        所以我们可以将原数组a[](这个数组没必要在程序中定义,事实上树状数组的叶子节点就是a[]数组,也可以发现其实并不是a[]数组中每个元素在树状数组中都有对应,虽然树状数组和a[]数组开的内存一样,但树状数组更多的时根节点,代表根节点下所有叶子节点的和) 当成一个差分数组,然后结合树状数组高效解决这个问题,要知道修改信息只需要求前缀和即可。

   代码

#include<iostream>
#include <cstring> 
using namespace std;
const int N = 100005;
#define lowbit(x) ((x)&-(x))

int segment_tree[N+1] = { 0 };

void update(int i, int d) {
	while (i <= N) {
		segment_tree[i] += d;
		i += lowbit(i);
	}
}

int sum(int x) {
	int ans = 0;
	while (x > 0) {
		ans += segment_tree[x];
		x -= lowbit(x);
	}
	return ans;
}

int main() {
	int n, l, r;
	while (cin >> n && n) {
		memset(segment_tree, 0, sizeof(segment_tree));
		for (int i = 1; i <= n; i++) {
			cin >> l >> r;
			//差分数组操作
			update(l, 1);
			update(r + 1, -1);
		}
		for (int i = 1; i <= n; i++) {
			//输出修改信息
			if (i != n) {
				cout << sum(i) << " ";
			}
			else {
				cout << sum(i) << endl;
			}
		}
	}
}

        有人肯定会问,你这也不是单点查询啊,如果这个sement_tree初始值不是0,你这不就炸了吗,好好好,满足你的要求,我们再看一道例题。

线段树 洛谷p3372

洛谷 p 3372

   问题描述

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

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

   问题分析        

        很容易想到,这题的暴力求解,但肯定会超时,可以使用线段树解决(之后发篇博客,先欠着),我们这里尝试用用树状数组+差分。但在此之前我们还需要做一些小学二年级的数学推倒,如果数组不是初始为0,那么只用一个差分数组是无法实现求前缀和的,因为一个树状数组只能算出每个元素的修改值,想到这里肯定有人反映过来了,我们求出变化量来加到原数组中不就行了吗,这个操作可以利用树状数组的update函数实现:

  • 也就是使用两个树状数组,
  • 一个用于求原数组中每个数的变化量,
  • 然后在另一个树状数组中利用update函数将这些变化量加到原数组上,
  • 然后再利用第二个树状数组的sum函数求前缀和

        不用数学推导也可以实现,这不失为一个方法,能想到说明你对树状数组有一定理解了,但这样操做会发现,编码特别麻烦,不信你可以试一试,这个操作不是全是修改,而是修改和查询操作有可能交替进行。所以还是推导一下好。

        我们已知差分就是前缀和的逆操作,那么我们就可以进行一下推导:

设原数组a[]的差分数组为d[]

那么前缀和:

sum(i) = a[1] + a[2] +......+a[i]

sum(i) = d[1] + ( d[1] + d[2] ) + ( d[1] + d[2] +d[3] )  + ... + ( d[1] + d[2] + ... +d[i] )

sum(i) = id[1] + (i-1)d[2] + ... + [i-(i-1)]d[i]

sum(i) = i ( d[1] + d[2] + ... +d[i] ) - ( 0d[1] + d[2] + 2d[3] + ... + (i-1)d[i] )

sum(i) = i \sum d[k] + \sum (k-1)d[k]     ( k=1~i )

        那我们就得到了一个公式:

sum(i) = i \sum d[k] + \sum (k-1)d[k]     ( k=1~i )

        那么我们就可以定义两个树状数组+差分数组一个表示d[k] ,另一个表示 (k-1)d[k]

        想要都得到区间[L,R]只需要sum(L)-sum(R-1)即可:

   代码


#include<iostream>
using namespace std;
#define ll long long
#define lowbit(x) ((x)&-(x))

const int N = 100005;

ll segment_tree1[N + 1] = { 0 };
ll segment_tree2[N + 1] = { 0 };

void update(int x, ll d, int v) {
	if (v == 1) {
		while (x <= N) {
			segment_tree1[x] += d;
			x += lowbit(x);
		}
	}
	else {
		while (x <= N) {
			segment_tree2[x] += d;
			x += lowbit(x);
		}
	}
}

ll sum(int x,int v) {
	ll ans = 0;
	if (v == 1) {
		while (x > 0) {
			ans += segment_tree1[x];
			x -= lowbit(x);
		}
	}
	else {
		while (x > 0) {
			ans += segment_tree2[x];
			x -= lowbit(x);
		}
	}
	return ans;
}

int main() {
	int n, m, c, R, L;
	ll e=0,old,v;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		old = e;
		cin >> e;
		update(i, e-old, 1);
		update(i, (i - 1) * (e - old), 2);
	}
	for (int i = 1; i <= m;i++) {
		cin >> c;
		if (c == 1) {
			cin >> L >> R >> v;
			update(L, v,1);
			update(R + 1, -v, 1);
			update(L, (L - 1) * v, 2);
			update(R + 1, -R * v, 2);
		}
		else {
			cin >> L >> R;
			cout << R * sum(R, 1) - sum(R, 2) - (L - 1) * sum(L - 1, 1) + sum(L - 1, 2) << endl;
		}
	}
	return 0;
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记022——Ubuntu 安装 MySQL8.0版本踩坑记录

目录 1、查看可安装 MySQL 版本 2、Ubuntu安装 MySQL8.0 3、MySQL8.0 区分大小写问题 4、MySQL8.0 设置sql_mode 5、MySQL8.0 改端口33060&#xff08;个人遇到问题&#xff09; 1、查看可安装 MySQL 版本 ## 列出可用的MySQL版本&#xff08;列出所有可用的MySQL版本以…

【数据结构】树——链式存储二叉树的基础

写在前面 书接上文&#xff1a;【数据结构】树——顺序存储二叉树 本篇笔记主要讲解链式存储二叉树的主要思想、如何访问每个结点、结点之间的关联、如何递归查找每个结点&#xff0c;为后续更高级的树形结构打下基础。不了解树的小伙伴可以查看上文 文章目录 写在前面 一、链…

qt之QFTP对文件夹(含嵌套文件夹和文件)、文件删除下载功能

一、前言 主要功能如下&#xff1a; 1.实现文件夹的下载和删除&#xff0c;网上很多资料都是单独对某个路径的文件操作的&#xff0c;并不能对文件夹操作 2.实现目标机中含中文名称自动转码&#xff0c;有些系统编码方式不同&#xff0c;下载出来的文件会乱码 3.实现ftp功能…

集群聊天服务器(7)数据模块

目录 Mysql数据库代码封装头文件与源文件 Mysql数据库代码封装 业务层代码不要直接写数据库&#xff0c;因为业务层和数据层的代码逻辑也想完全区分开。万一不想存储mysql&#xff0c;想存redis的话&#xff0c;就要改动大量业务代码。解耦合就是改起来很方便。 首先需要安装m…

手机远程控制电脑,让办公更快捷

在数字化办公的浪潮下&#xff0c;远程控制软件已成为连接工作与生活的桥梁。它使得用户能够通过一台设备&#xff08;主控端&#xff09;来操作另一台设备&#xff08;被控端&#xff09;&#xff0c;无论它们是否位于同一局域网内。这种软件广泛应用于远程办公、手机远程控制…

面向FWA市场!移远通信高性能5G-A模组RG650V-NA通过北美两大重要运营商认证

近日&#xff0c;全球领先的物联网整体解决方案供应商移远通信宣布&#xff0c;其旗下符合3GPP R17标准的新一代5G-A模组RG650V-NA成功通过了北美两家重要运营商认证。凭借高速度、大容量、低延迟、高可靠等优势&#xff0c;该模组可满足CPE、家庭/企业网关、移动热点、高清视频…

72项!湖北省2024年度第二批省级科技计划项目拟立项项目公示!

本期精选 SCI&EI ●IEEE 1区TOP 计算机类&#xff08;含CCF&#xff09;&#xff1b; ●EI快刊&#xff1a;最快1周录用&#xff01; 知网(CNKI)、谷歌学术期刊 ●7天录用-检索&#xff08;100%录用&#xff09;&#xff0c;1周上线&#xff1b; 免费稿件评估 免费匹配…

LeetCode 热题 100 回顾

目录 一、哈希部分 1.两数之和 &#xff08;简单&#xff09; 2.字母异位词分组 &#xff08;中等&#xff09; 3.最长连续序列 &#xff08;中等&#xff09; 二、双指针部分 4.移动零 &#xff08;简单&#xff09; 5.盛最多水的容器 &#xff08;中等&#xff09; 6…

Chrome 浏览器 131 版本开发者工具(DevTools)更新内容

Chrome 浏览器 131 版本开发者工具&#xff08;DevTools&#xff09;更新内容 一、使用 Gemini 调试 CSS Chrome DevTools 现在推出了一个新的实验性 AI 辅助面板&#xff0c;可以与 Gemini 聊天并获得帮助来调试 CSS。 在 Elements 面板中&#xff0c;右键点击一个元素并选…

网络编程-002-UDP通信

1.UDP通信的简单介绍 1.1不需要通信握手,无需维持连接,网络带宽需求较小,而实时性要求高 1.2 包大小有限制,不发大于路径MTU的数据包 1.3容易丢包 1.4 可以实现一对多,多对多 2.客户端与服务端=发送端与接收端 代码框架 收数据方一般都是客户端/接收端 3.头文件 #i…

websocket身份验证

websocket身份验证 前言 上一集我们就完成了websocket初始化的任务&#xff0c;那么我们完成这个内容之后就应该完成一个任务&#xff0c;当客户端与服务端连接成功之后&#xff0c;客户端应该主动发起一个身份认证的消息。 身份认证proto 我们看一眼proto文件的内容。 我…

初识C++(1)

C是在C语言的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库以及编程范式等。 在C语言中&#xff0c;变量、函数和类的名称存在于全局作用域中&#xff0c;因此可能会发生许多冲突。比如&#xff1a; #include<stdio.h> #include<…

Axure9生成的阅览页面如何自动展开左侧页面导航?

问题 Axure9生成的阅览页面&#xff0c;默认情况是自动折叠的&#xff0c;如何自动展开左侧页面导航&#xff1f; 解决 Axure工具&#xff1a;发布 > 预览选项 > 播放器 > 打开页面列表

LeetCode:700. 二叉搜索树中的搜索

目录 题目描述: 代码: 题目描述: 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则返回 null 。 示例 1: 输入&#xff1a;root [4,2,7,1,3…

架构图解析:如何构建高效的微服务系统

在当今的数字化浪潮中&#xff0c;构建高效、灵活且可扩展的系统已成为企业的重要目标。微服务架构作为一种先进的软件设计模式&#xff0c;通过将复杂的应用程序分解为一系列小型、独立的服务&#xff0c;显著提升了系统的灵活性、可扩展性和维护性。本文将通过解析微服务系统…

【Android、IOS、Flutter、鸿蒙、ReactNative 】实现 MVP 架构

Android Studio 版本 Android Java MVP 模式 参考 模型层 model public class User {private String email;private String password;public User(String email, String password) {this.email = email;this.password = password;}public String getEmail() {return email;}…

【海思Hi3519DV500】双目网络相机套板硬件规划方案

Hi3519DV500双目网络相机套板是针对该芯片设计的一款 IP 编码板 PCBA&#xff0c;硬件接口支持双目sensor 接入&#xff0c;SDIO3.0 接口、USB2.0、USB3.0、UART 接口以及丰富的 IO 扩展应用&#xff0c;可根据各种使用场景设计相应扩展板&#xff0c;丰富外围接口&#xff0c;…

百度世界2024:智能体引领AI应用新纪元

在近日盛大举行的百度世界2024大会上&#xff0c;百度创始人李彦宏以一场题为“文心一言”的精彩演讲&#xff0c;再次将全球科技界的目光聚焦于人工智能&#xff08;AI&#xff09;的无限可能。作为一名科技自媒体&#xff0c;我深感这场演讲不仅是对百度AI技术实力的一次全面…

SPP:空间金字塔池化

今天水一篇博客&#xff0c;讲讲SPP池化结构&#xff1b;那这是个什么东西呢&#xff1f;它的作用又是什么呢&#xff1f;在了解它之前我们先简单了解一下大部分的神经网络&#xff1b; 引入&#xff1a; 在大部分的神经网络中&#xff0c;都将神经网络分为Backbone主干网络、…

Ubuntu Linux使用前准备动作_使用root登录图形化界面

Ubuntu默认是不允许使用 root 登录图形化界面的。这是出于安全考虑的设置。但如果有需要&#xff0c;可以通过以下步骤来实现使用 root 登录&#xff1a; 1、设置 root 密码 打开终端&#xff0c;使用当前的管理员账户登录系统。在终端中输入命令sudo passwd root&#xff0c…