树状数组+例题

news2024/11/22 10:32:13

一、树状数组的定义

树状数组二元索引树(Binary Indexed Tree),现多用于高效计算数列的前缀和, 区间和。它可以以 l o g ( n ) log(n) log(n) 的时间得到任意前缀和,也支持在 l o g ( n ) log(n) log(n)时间内支持动态单点值的修改。空间复杂度 O ( n ) O(n) O(n)

二、树状数组的作用

树状数组最核心的两个操作分别是;

  • int sum(int idx):以 l o g ( n ) log(n) log(n) 的时间返回,从 (1 ~ idx) 的前缀和。
  • void add(int idx,int k):以 l o g ( n ) log(n) log(n) 的时间,进行单点修改。

其他的一些操作都是在这两个函数基础上的拓展。

三、树状数组的结构

t 是前缀和数组,a 是原数组,建立如下的树状数组结构。

在这里插入图片描述
根据图我们可以发现:

  • t [ 1 ] = a [ 1 ] t[1] = a[1] t[1]=a[1]
  • t [ 2 ] = t [ 1 ] + a [ 2 ] t[2] = t[1] + a[2] t[2]=t[1]+a[2]
  • t [ 3 ] = a [ 3 ] t[3] = a[3] t[3]=a[3]
  • t [ 4 ] = t [ 2 ] + t [ 3 ] + a [ 4 ] t[4] = t[2] + t[3] + a[4] t[4]=t[2]+t[3]+a[4]
  • t [ 5 ] = a [ 5 ] t[5] = a[5] t[5]=a[5]
  • t [ 6 ] = t [ 5 ] + a [ 6 ] t[6] = t[5] + a[6] t[6]=t[5]+a[6]
  • t [ 7 ] = a [ 7 ] t[7] = a[7] t[7]=a[7]
  • t [ 8 ] = t [ 4 ] + t [ 6 ] + t [ 7 ] + a [ 8 ] t[8] = t[4] + t[6] + t[7] + a[8] t[8]=t[4]+t[6]+t[7]+a[8]

所以 t[8] 就是整个数组的前缀和。

此外还有:

  • 每一个结点覆盖的长度都是 lowbit(x)(这个函数是求 x 二进制位的最后一位1,比如 x = 8,lowbit(x (1000) = 8,所以图中 t[8] 结点覆盖的长度是8;比如 x = 6,lowbit(6 (0110) ) = 2 ,所以图中 t[6] 结点覆盖的长度是2)。
  • t[x] 结点的父结点就是 t[x + lowbit(x)] (比如当 x = 1时,t[1] 的父结点为 t[1 + lowbit(0001) = 2 ],t[2] 的父结点是 t[2 + lowbit(0010) = 4];当 x = 5时,t[5] 的父结点为 t[5 + lowbit(0101) = 6],t[6] 的父结点为 t[6 + lowbit(0110) = 8] )。

四、树状数组的操作

1. lowbit

根据其意思我们可以知道,这个函数就是求 一个数的最低位的(这里的最低位指的是,最低位的1)。

那么我们要如何得到一个数 1 的最低位呢?

这里有一个数 x = 3,其二进制位是 0000 0011(只显示了低8位)
我们可以先将 x 取反,得到 ~x = 1111 1100
再将取反的 ~x + 1 = 1111 1101
最后将 x & (~x + 1) ,结果如下
0000 0011
1111 1101
0000 0001
我们发现如此一来就得到了 x 的最低位的1
实际上 (~x + 1) 就是 -x
所以 lowbit(x) 的操作 相当于返回 x&(-x)

所以我们可以写出 lowbit 的代码:

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

2. 单点修改

void add(int idx,int k)表示将数组中第 idx个数加上 k

这里以 add(3,5)为例:

在这里插入图片描述
根据这张图可以发现:t[3] 影响了它的父结点 t[4],t[4] 又影响了它的父结点 t[8]。

所以如果我们要让 t[3] 加上 k,那么在这一条路径上的所有结点都要加上 k。之前我们已经知道了一个结点的父结点如何求,所以 add 的代码如下:

void add(int idx, int k)
{
    // i + lowbit(i)  就是结点 i 的父结点
    //我们要让这一条路径上的每一个点都加上 k
    for(int i = idx; i <= n; i += lowbit(i))
        t[i] += k;
}

3.求前缀和

int sum(int idx)求取前 idx个数的前缀和。

这里以 sum(7)为例:

在这里插入图片描述
据图我们可以发现 s u m ( 7 ) = t [ 7 ] + t [ 6 ] + t [ 4 ] sum(7) = t[7] + t[6] + t[4] sum(7)=t[7]+t[6]+t[4],我们观察 7(0111) , 6(0110) , 4(0100) 的二进制位可以发现,后面的一个数y 都等于 前面的一个数 x − l o w b i t ( x ) x - lowbit(x) xlowbit(x)。即 7 − l o w b i t ( 7 ) = 6 , 6 − l o w b i t ( 6 ) = 4 , 4 − l o w b i t ( 4 ) = 0 7 - lowbit(7) = 6 , 6 - lowbit(6) = 4, 4 - lowbit(4) = 0 7lowbit(7)=6,6lowbit(6)=4,4lowbit(4)=0

我们可以写出如下的代码:

int sum(int idx)
{
    int ans = 0;
    for(int i = idx; i; i -= lowbit(i))
        ans += t[i];
    return ans;
}

五、例题

题目链接

P3374 【模板】树状数组 1

题目描述

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

  • 将某一个数加上 x x x

  • 求出某区间每一个数的和

输入格式

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

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

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

1 x k 含义:将第 x 个数加上 k

2 x y 含义:输出区间 [x,y] 内每个数的和

输出格式

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

输入输出样例

输入

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

输出

14
16

说明/提示

【数据范围】

1 ≤ n , m ≤ 5 × 1 0 5 1≤n,m≤5×10^5 1n,m5×105

直接套用模板即可,代码如下:

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

const int N = 5e5+10;
//c即前缀和数组 ,a 即原数组
int c[N],a[N],n,m;

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

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

int sum(int x){
    int ans = 0;
    for(int i = x;i;i -= lowbit(i)) ans += c[i];
    return ans;
}

int main(){
    cin>>n>>m;
    for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
    for(int i = 1;i <= n;i++) add(i,a[i]);
    
    while(m--){
        int op;
        scanf("%d",&op);
        if(op == 1){
            int x,k;
            scanf("%d%d",&x,&k);
            add(x,k);
        }
        else{
            int l,r;
            scanf("%d%d",&l,&r);
            //求具体某一区间和公式为 s(r) - s(l-1)
            cout<<sum(r) - sum(l-1)<<endl;
        }
    }
    return 0;
}

参考链接

楼兰图腾题解

更多例题

P3368 【模板】树状数组 2
楼兰图腾
A Tiny Problem with intergers
A Simple Problem with Integers
Lost Cows

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

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

相关文章

Spring简介与使用

什么是spring spring是一个开源的框架&#xff0c;里面有一系列功能&#xff0c;可以使我们的开发变得更为轻松 简单来说&#xff0c;spring是包含众多工具方法的IoC容器 所谓容器&#xff0c;就是盛放东西的事务&#xff0c;例如我们的ArrayList就是数据存储的容器&#xff…

数据库——排序与分页

目录 排序数据 单列排序 多列排列 分页 分页原理 优点 MySQL 8.0新特性 排序数据 使用 ORDER BY 子句排序ASC&#xff08;ascend&#xff09;: 升序DESC&#xff08;descend&#xff09;:降序ORDER BY 子句在SELECT语句的结尾。 单列排序 SELECT employee_id,last_name…

【Linux】项目自动化构建工具-make与Makefile的简单使用(模拟实现进度条)

目  录1 make与Makefile使用2 模拟实现进度条前言&#xff1a; 会不会编写Makefile&#xff0c;从侧面说明了一个人是否具备完成大型工程的能力。一个工程中的源文件不计其数&#xff0c;按类型、功能、模块分别放在若干个目录中&#xff0c;Makefile定义了一系列的规则来指定…

使用 EMQX Cloud 桥接数据到 GCP Pub/Sub

前不久&#xff0c;Google 宣布其旗下的 GCP IoT Core 即将在 2023 年 8 月 16 日停止提供服务。这意味着大量使用 GCP IoT Core 的用户可能需要将他们的 IoT 应用迁移到其他物联网云服务。除了云服务的迁移&#xff0c;很多用户也在直接利用谷歌云生态&#xff0c;使用 GCP 上…

Docker部署 Harbor

系列文章目录 Docker部署 registry Docker搭建 svn Docker部署 Harbor Docker 部署SQL Server 2017 Docker 安装 MS SqlServer Docker部署 Oracle12c Docker部署Jenkins Docker部署 Harbor系列文章目录前言一、Harbor安装有3种方式二、安装步骤1. 从github官方地址下载安装包2…

C/C++尖括号和双引号包含头文件的区别

前言头文件有两种包含方式&#xff0c;一种是使用尖括号<>&#xff0c;另外一种是通过双引号""包含&#xff0c;例如&#xff1a;#include <iostream> #include "add.h"那么今天就专门来聊一聊这两种方式的区别。1.头文件的含义不同使用尖括号…

多表连接查询

语法&#xff1a; select ... from a join b on a和b的连接条件 join c on a和c的连接条件 join d on a和d的连接条件 一条SQL语句中内连接与外连接可以混合使用 案例&#xff1a;查询每个员工的部门名称以及薪资等级&#xff0c;要求显示员工名、部门名、薪资和薪资等级 SQL&…

核心乐理---和弦基础

和弦命名 什么是和弦 三个或三个以上的三度堆叠成为和弦 三和弦 三和弦指的是有三个音的和弦 七和弦是指七度的和弦&#xff0c;共4个音 其余的和弦与七和弦命名规则相同&#xff0c;跨越几度就是几和弦 九和弦是指跨越九度的和弦&#xff0c;共5个音十一和弦是指跨越十一…

【阶段三】Python机器学习18篇:机器学习项目实战:AdaBoost算法的核心思想、原理与数学原理举例

本篇的思维导图: AdaBoost算法的核心思想 AdaBoost算法(Adaptive Boosting)是一种有效而实用的Boosting算法,它以一种高度自适应的方式按顺序训练弱学习器。针对分类问题,AdaBoost算法根据前一次的分类效果调整数据的权重,在上一个弱学习器中分类错误的样本的权…

和Nginx相关的TCP/IP中反向代理系统解析与调优

文章目录前言反向代理系统分析消耗资源分析CPUMemory网卡压测与调优实战压测数据对比什么是TIME_WAITkeepalive开启前后数据对比小结关于后续端口不足&#xff0c;限制连接扩展协议层面无法充分做到连接的复用TCP优化分享相关文章&#xff1a;前言 本文介绍aeproxy这个应用以及…

IB地理科学什么?

IB地理科是一门很特别的科目&#xff0c;能帮助同学掌握技巧认识和了解这世界&#xff0c;而这课程分为两部分&#xff0c;包括自然环境和人文社会。IB地理科两部分 首先是自然环境&#xff0c;包括生态系统、气候&#xff0c;地壳活动等等 &#xff1b;另外是人文社会&#x…

Nosql和Redis介绍,Redis五大数据类型及操作,跳跃表

NoSQL数据库是用来解决性能问题的&#xff0c;分很多类。redis是NoSQL的一种。 NoSQL的引入&#xff1a; 随着Web2.0时代的到来。可以进行网络请求的不仅限与电脑。用户还可以通过手机端&#xff0c;平板甚至汽车等来进行网络请求。网络请求极具增加&#xff0c;增加了服务器…

【代码随想录】LC 102. 二叉树的层序遍历

目录 一、题目 1、原题链接 2、题目描述 二、解题报告 1、思路分析 2、时间复杂度 3、代码详解 三、知识风暴 一、题目 1、原题链接 力扣 2、题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问…

linux下miniconda环境的配置以及软件的安装

miniconda 我们需要在自己的目录下安装conda环境&#xff0c;所以需要自定义安装位置 mkdir /share/nas6/wangyq/biosoft/miniconda wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /share/nas6/wangyq/biosoft/miniconda/miniconda.sh注意…

070-JAVA项目实训:仿QQ即时通讯软件系列讲座五(讲解用户注册功能)

【上一讲】069-JAVA项目实训:仿QQ即时通讯软件讲座四(讲解系统登录功能)_CSDN专家-赖老师(软件之家)的博客-CSDN博客 本文主要内容是实现注册QQ用户功能,自动获取本机IP地址,与系统用户判断端口是否唯一,使用的主要技术如下: 1.使用数据库技术完成注册功能; 2.自动…

Linux系统编程——锁

目录线程同步:锁的使用:使用matex(互斥量、互斥锁)一般步骤:restrict关键字:死锁:读写锁&#xff1a;条件变量实现生产者-消费者代码信号量实现消费者生产者线程同步: 协同步调&#xff0c;对公共区域数据按序访问。防止数据混乱&#xff0c;产生与时间有关的错误。 锁的使用…

原来这些技术标准,是字节跳动人参与制定的

我们常常遇到这类问题&#xff1a;因为不同厂商产品标准不同、接口不一致&#xff0c;导致 A 产品无法兼容 B 产品&#xff0c;C 接口和 D 接口无法连接&#xff0c;实在令人困扰。其实&#xff0c;技术从业者们一直在试图解决这些不统一、不兼容的问题&#xff0c;他们的方法是…

ceres学习笔记(一)

本来还想着先对照着官方doc来学习的。突然在csdn里面搜了下&#xff0c;发现了几篇高质量的文章&#xff0c;就先对应这几篇文章学习&#xff0c;来快速入门。 一、ceres求解问题一般步骤 使用ceres-solver求解优化问题一般分为下面三步&#xff1a; 1.第一步&#xff1a;构…

aws cdk 配置 lambda 函数的金丝雀发布

之前的文章介绍了使用sam框架完成lambda函数的金丝雀发布&#xff0c;这里使用cdk创建lambda函数项目实现此功能 Building CI/CD pipelines for lambda canary deployments using AWS CDK 项目的结构如下图所示 lambda堆栈示例 应用程序和环境配置 #!/usr/bin/env python3 im…

数据结构与算法0—大纲

数据结构&#xff1a;数据结构是计算机存储、组织数据的方式。是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组. 算法&#xff1a;是指解题方案的准确而完整的描述&#xff0c;是一系列解决问题的清晰指令&#xff0c;算法代表着用系统的方法…