快乐地打牢基础(4)——树状数组

2023-12-30 06:38

本文主要是介绍快乐地打牢基础(4)——树状数组,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在解题的过程中,我们想维护一个数组的前缀和s[i] = A[1] + A[2] +…+A[i]。我们改变任意一个A[i],那么S[i]之后都会发生变化,朴素写法调整前缀和S最坏的情况需要O(n)的时间。所以引入树状数组,它的修改和求和都是O(logn)的,效率非常高。

一、基本思想


根据任意正整数关于2的不重复次幂的唯一分解性质,若一个正整数x的二进制表示为10101,其中等于1 的位置是0,2,4,那么正整数x可以被“二进制分解”成2 ^ 4 + 2 ^ 2 + 2^0。区间[1,x]可以被分成O(logx)个小区间。
长度为2 ^ 4的小区间[1,2 ^ 4]。
长度为2 ^ 2的小区间[2 ^ 4+1,2 ^ 4+2 ^ 2]。
长度为2 ^ 0的小区间[2 ^ 4+2 ^ 2 + 2,2 ^ 4+2 ^ 2 + 2 ^ 0]。

对于给定序列A,我们建立一个数组从,其中c[x]保存序列A的区间[x-lowbit(x)+1,x]中所有的和。
形成下图的树形结构:
在这里插入图片描述
从图中可以得到C[]和A[]关系:C[i]=A[i-2^ k+1]+A[i-2^k+2]+…A[i]; (k为i的二进制中末尾0的数量)
该结构满足一下的性质:

  • 1.每个内部结点C[x]保存以它为根的子树中所有叶结点的和。
  • 2.每个内部结点C[x]的子节点个数等于lowbit(x)的位数。
  • 3.除树根外,每个内部节点C[x]的父结点是C[x + lowbit(x)].
  • 4.树的深度为O(logN)。

二、算法操作


由于之前已经整理过树状数组原理的博客:https://blog.csdn.net/sinat_40872274/article/details/97895705
所以直接给出重要操作。
例如i=8时,k=3;

1.求lowbit(x)

lowbit(x)表示取出x的最低位1 换言之 lowbit(x)=2^k ( k为k为x的二进制中末尾0的数量)。
lowbit(x) = x&(-x)是怎么来的呢?具体分析一下

设x > 0,x的第k位上是最低位的1,后边还有若干个0。
先把x取反,此时第k位变成0,第0~到k-1位都是1。
再令x = x +1,此时因为进位,第k位变成1,第0~k-1位都是0.在上面的取反加1的 操作后,x的第k+1位到最高位因为取反,所以是与原来相反的,这样一来x&( ~ x+1)运算过后就只有第k位上是1了。而补码 ~x = -x-1 ,因此lowbit(x) = x&(-x)。
代码:


int lowbit(int x){return x&-x;
}
2.对某个元素进行加法操作

树状数组支持单点增加,意思是给序列中的某个数A[x]加上y,同时正确维护序列的前缀和,根据上面给出的树形结构和它的性质,只有结点C[x]及其所有祖先你结点保存的“区间和”包含结点A[x],而任意一个结点的祖先至多只有logN个,所以逐一对包含A[x]的C[]值进行更新就可以了,这样可以在O(logN)时间内执行单点增加操作。
那么我们的现在就是要解决如和找到A[x]的祖先结点。这就要回顾这个树形结构的第三点性质了。
在这里插入图片描述

  • 3.除树根外,每个内部节点C[x]的父结点是C[x + lowbit(x)]。

代码:

void update(int x,int y){for(;x<= N; x += lowbit(x)) c[x] += y;return ans;
}
3.查询前缀和

树状数组支持查询前缀和,即序列A第1~x个数的和。按照我们刚才提出的方法,应该求出x的二进制表示中每个等于1的位,把[1,x]分成O(logN)个小区间,而每个小区间的和都已经存在数组C[]中了,所以直接求代表每个小区间的C[]的和就行了。

int sum(int x){int ans = 0;for(; x; x -= lowbit(x)) ans += c[x];return ans;//可以发现求前缀和与加法操作 是两个逆序的过程
}
4.统计A[x]…A[y]的值

前缀和思想:sum(y) - sum(x-1)

5.多维树状数组

将一维的树状数组变成m维的,那么时间复杂度就是O((logN)^m),在m不大的时候,还是可以接受的。扩充的方法就是将原来的修改和查询函数中的一个循环,改成m个循环m维数组c中的操作。下面以n*m的二维数组a,树状数组c为例,给出单点增加和求和操作的代码。

//单点增加
void update(int x,int y ,int z){//将(x,y)的值加上zint i = x;while(i <= n){int j = y;while(j <= m

这篇关于快乐地打牢基础(4)——树状数组的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/552017

相关文章

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

hdu 1166 敌兵布阵(树状数组 or 线段树)

题意是求一个线段的和,在线段上可以进行加减的修改。 树状数组的模板题。 代码: #include <stdio.h>#include <string.h>const int maxn = 50000 + 1;int c[maxn];int n;int lowbit(int x){return x & -x;}void add(int x, int num){while

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close

C语言:柔性数组

数组定义 柔性数组 err int arr[0] = {0}; // ERROR 柔性数组 // 常见struct Test{int len;char arr[1024];} // 柔性数组struct Test{int len;char arr[0];}struct Test *t;t = malloc(sizeof(Test) + 11);strcpy(t->arr,

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

C 语言基础之数组

文章目录 什么是数组数组变量的声明多维数组 什么是数组 数组,顾名思义,就是一组数。 假如班上有 30 个同学,让你编程统计每个人的分数,求最高分、最低分、平均分等。如果不知道数组,你只能这样写代码: int ZhangSan_score = 95;int LiSi_score = 90;......int LiuDong_score = 100;int Zhou

c++基础版

c++基础版 Windows环境搭建第一个C++程序c++程序运行原理注释常亮字面常亮符号常亮 变量数据类型整型实型常量类型确定char类型字符串布尔类型 控制台输入随机数产生枚举定义数组数组便利 指针基础野指针空指针指针运算动态内存分配 结构体结构体默认值结构体数组结构体指针结构体指针数组函数无返回值函数和void类型地址传递函数传递数组 引用函数引用传参返回指针的正确写法函数返回数组

【QT】基础入门学习

文章目录 浅析Qt应用程序的主函数使用qDebug()函数常用快捷键Qt 编码风格信号槽连接模型实现方案 信号和槽的工作机制Qt对象树机制 浅析Qt应用程序的主函数 #include "mywindow.h"#include <QApplication>// 程序的入口int main(int argc, char *argv[]){// argc是命令行参数个数,argv是