【笔记】树状数组

Start

【笔记】树状数组 目录

      • 简介
      • 引入
        • 1. 直接暴力
        • 2. 维护前缀和数组
        • 总结
      • 定义
      • 前置知识: lowbit ⁡ \operatorname{lowbit} lowbit 操作
      • 区间的表示方法
      • 操作
        • 单点修改
        • 前缀和查询
        • 任意区间查询
      • 例题1: 单点修改,区间查询
      • 例题2: 区间修改,单点查询
      • 例题3: 区间修改,区间查询
        • (后附极限卡常代码,70ms,较优解)


简介

树状数组是一种树形数据结构,支持在 O ( log ⁡ n ) O(\log n) O(logn) 的时间复杂度内进行 单点修改查询前缀和 的操作。

  • 优点:常数小,码量小,操作灵活简便。
  • 缺点:只能用来维护具有 结合律可差分 的信息。例如:区间和、积等,而不能维护区间最大(最小)值。

引入

现在想要让你实现两个操作:

  1. 单点修改
  2. 查询 [ 1 , x ] [1,x] [1,x] 的和

在没有学过树状数组的时候你会怎么做?

1. 直接暴力

单点修改虽然方便,但前缀和是 O ( n ) O(n) O(n) 复杂度。

2. 维护前缀和数组

这样做虽然查询是 O ( 1 ) O(1) O(1) 了,但单点修改又是 O ( n ) O(n) O(n)

总结

  • 暴力
    • 修改: O ( 1 ) O(1) O(1)
    • 查询: O ( n ) O(n) O(n)
  • 前缀和
    • 修改: O ( n ) O(n) O(n)
    • 查询: O ( 1 ) O(1) O(1)

那么我们不妨考虑一个折中的办法,两种操作都是 O ( log ⁡ n ) O(\log n) O(logn) 的复杂度。


定义

树状数组

注:这里的数值表示的是该区间所有元素的和,也就是这个节点左下方的所有直接相关节点的总和。
例如:权值为 31 31 31 的节点表示的是权值分别为 19 , 10 , 1 19,10,1 19,10,1 的节点以及原数组中下表为 8 8 8 的元素之和。

显然,我们能求出原数组为

[ 8 , 6 , 1 , 4 , 5 , 5 , 1 , 1 , 3 , 2 , 1 , 4 , 9 , 0 , 7 , 4 ] [8,6,1,4,5,5,1,1,3,2,1,4,9,0,7,4] [8,6,1,4,5,5,1,1,3,2,1,4,9,0,7,4]

这里插一句话:树状数组可以近似看成线段树去掉所有右儿子构成的树。


前置知识: lowbit ⁡ \operatorname{lowbit} lowbit 操作

一个二进制数的 lowbit ⁡ \operatorname{lowbit} lowbit 值就是这个数末尾第一个非零的位置的权值。

举个例子: 10001 0 ( 2 ) 100010_{(2)} 100010(2)

这个数的 lowbit ⁡ \operatorname{lowbit} lowbit 值是 1 0 ( 2 ) 10_{(2)} 10(2),即 2 ( 10 ) 2_{(10)} 2(10)

那么这个怎么用代码实现呢?

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

什么?你问为什么这么简单??

这都不知道,赶紧退役吧 h h \color{white}{这都不知道,赶紧退役吧hh} 这都不知道,赶紧退役吧hh

这里涉及到补码的概念。

一个二进制数的补码就是其二进制上的每一位都按位取反之后再 + 1 +1 +1

还是那个数: 10001 0 ( 2 ) 100010_{(2)} 100010(2)

先按位取反: 01110 1 ( 2 ) 011101_{(2)} 011101(2)

再加一: 1111 0 ( 2 ) 11110_{(2)} 11110(2)

我们惊奇地发现,它们的后两位竟然是一样的!!!

我们把它们进行按位与运算 &,得到的结果是 1 0 ( 2 ) 10_{(2)} 10(2),即 2 ( 10 ) 2_{(10)} 2(10),与我们刚才进行手动 lowbit ⁡ \operatorname{lowbit} lowbit 运算的结果相同。

在计算机的运算过程中,由于是按照补码储存的,所以我们需要的 ~x + 1 就可以写成 -x

因此 lowbit ⁡ \operatorname{lowbit} lowbit 才能写成 x & -x


区间的表示方法

对于每个标号为 x x x 的节点,我们发现它父节点的标号为 x + lowbit  x x+\text{lowbit}\ x x+lowbit x

而每个区间的范围都是 ( x − lowbit ( x ) , x ] (x-\text{lowbit}(x),x] (xlowbit(x),x]


操作

单点修改

对于每个被修改的点,我们需要找到它的所有祖先节点并都进行修改操作。

考虑到它们标号的关系,我们只要每次加一个 lowbit(x) \text{lowbit(x)} lowbit(x) 就能找到所有祖先节点了。

代码:

void add(int x, int c) // 将第 x 个数加 c
{for (int i = x; i <= n; i += lowbit(i))tr[i] += c;
}

前缀和查询

实践是检验真理的唯一标准。

经过我们的实践,找到该节点前面的所有节点,只需要每次减 lowbit(x) \text{lowbit(x)} lowbit(x) 即可。

代码:

void query(int x) // 查询 1~x 的和
{int res = 0;for (int i = x; i; i -= lowbit(i))res += tr[i];return res;
}

任意区间查询

我们都知道前缀和的性质。

∑ i = l r w i = ∑ i = 1 r w i − ∑ i = 1 l − 1 w i \sum_{i=l}^{r}w_i=\sum_{i=1}^{r}w_i-\sum_{i=1}^{l-1}w_i i=lrwi=i=1rwii=1l1wi

代码:

void Query(int l, int r) // 查询 [l,r] 的和
{return query(r) - query(l - 1);
}

例题1: 单点修改,区间查询

原题链接:P3374 【模板】树状数组 1

操作和上面的相同,直接上代码:

#include <iostream>using namespace std;const int N = 500010;int n, m;
int a[N];
int tr[N];int lowbit(int x)
{return x & -x;
}void add(int x, int c)
{for (int i = x; i <= n; i += lowbit(i))tr[i] += c;
}int sum(int x)
{int res = 0;for (int i = x; i; i -= lowbit(i))res += tr[i];return res;
}int main()
{int op, x, y;scanf("%d%d", &n, &m);for (int i = 1; i <= n; i ++ )scanf("%d", &a[i]), add(i, a[i]);while (m -- ){scanf("%d%d%d", &op, &x, &y);if (op == 1) add(x, y);else printf("%d\n", sum(y) - sum(x - 1));}return 0;
}

例题2: 区间修改,单点查询

原题链接:P3368 【模板】树状数组 2

同一道题,思路已经在昨天的 【笔记】线段树 里面讲了,无非是维护一个差分数组。

代码:

#include <iostream>using namespace std;const int N = 500010;int n, m;
int a[N], b[N];
int tr[N];int lb(int x)
{return x & -x;
}void add(int x, int v)
{for (int i = x; i <= n; i += lb(i))tr[i] += v;
}int q(int x)
{int res = 0;for (int i = x; i; i -= lb(i))res += tr[i];return res;
}int main()
{cin >> n >> m;for (int i = 1; i <= n; i ++ )cin >> a[i], b[i] = a[i] - a[i - 1], add(i, b[i]);while (m -- ){int op, x, y, k;cin >> op >> x;if (op == 1){cin >> y >> k;add(x, k), add(y + 1, -k);}else cout << q(x) << endl;}
}

例题3: 区间修改,区间查询

原题链接:P3372 【模板】线段树 1

不要说我用线段树的题练习树状数组,我找不到树状数组的模板题才用的这个

考虑用树状数组 tr[] 维护差分数组

则求原数组的前缀和

{ a 1 = d 1 a 2 = d 1 + d 2 a 3 = d 1 + d 2 + d 3 . . . . . . a n = d 1 + d 2 + . . . + d n \left\{\begin{matrix} a_1& =& d_1& & & & & & & \\ a_2& =& d_1& +& d_2& & & & & \\ a_3& =& d_1& +& d_2& +& d_3& & & \\ .& .& .& .& .& .& & & & \\ a_n& =& d_1& +& d_2& +& ...& +& d_n& \\ \end{matrix}\right. a1a2a3.an===.=d1d1d1.d1++.+d2d2.d2+.+d3...+dn

s i = ∑ i = 1 n a i = { d 1 d 1 + d 2 d 1 + d 2 + d 3 . . . . . . d 1 + d 2 + . . . + d n s_i=\sum_{i=1}^{n}a_i=\left\{\begin{matrix} d_1& & & & & & & \\ d_1& +& d_2& & & & & \\ d_1& +& d_2& +& d_3& & & \\ .& .& .& .& .& .& & & & \\ d_1& +& d_2& +& ...& +& d_n& \\ \end{matrix}\right. si=i=1nai= d1d1d1.d1++.+d2d2.d2+.+d3.....+dn

我们考虑把后面的矩阵补全:


s i = ( n + 1 ) × ∑ i = 1 n d i − ∑ i = 1 n ( i × d i ) s_i=(n+1) \times \sum_{i=1}^{n}d_i-\sum_{i=1}^{n}(i \times d_i) si=(n+1)×i=1ndii=1n(i×di)

所以我们需要两个树状数组,tr1[] 维护差分数组,tr2[] 维护 i × d i i \times d_i i×di

代码:

#include <iostream>using namespace std;typedef long long LL;const LL N = 1000010;LL n, m;
LL a[N];
LL t1[N], t2[N];inline LL lowbit(LL x)
{return x & -x;
}inline void add(LL t[], LL x, LL c)
{for (LL i = x; i <= n; i += lowbit(i))t[i] += c;
}inline LL sum(LL t[], LL x)
{LL res = 0;for (LL i = x; i; i -= lowbit(i))res += t[i];return res;
}inline LL psum(LL x)
{return sum(t1, x) * (x + 1) - sum(t2, x);
}int main()
{scanf("%lld%lld", &n, &m);for (LL i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);for (LL i = 1; i <= n; i ++ ){LL b = a[i] - a[i - 1];add(t1, i, b);add(t2, i, b * i);}while (m -- ){char op[2];LL l, r, d;scanf("%s%lld%lld", op, &l, &r);if (op[0] == '2'){printf("%lld\n", psum(r) - psum(l - 1));}else{scanf("%lld", &d);add(t1, l, d), add(t2, l, l * d);add(t1, r + 1, -d), add(t2, r + 1, -d * (r + 1));}}return 0;
}

最后,如果觉得对您有帮助的话,点个赞再走吧!

(后附极限卡常代码,70ms,较优解)

#define qwq optimize
#pragma GCC qwq(1)
#pragma GCC qwq(2)
#pragma GCC qwq(3)
#pragma GCC qwq("Ofast")
#pragma GCC qwq("inline")
#pragma GCC qwq("-fgcse")
#pragma GCC qwq("-fgcse-lm")
#pragma GCC qwq("-fipa-sra")
#pragma GCC qwq("-ftree-pre")
#pragma GCC qwq("-ftree-vrp")
#pragma GCC qwq("-fpeephole2")
#pragma GCC qwq("-ffast-math")
#pragma GCC qwq("-fsched-spec")
#pragma GCC qwq("unroll-loops")
#pragma GCC qwq("-falign-jumps")
#pragma GCC qwq("-falign-loops")
#pragma GCC qwq("-falign-labels")
#pragma GCC qwq("-fdevirtualize")
#pragma GCC qwq("-fcaller-saves")
#pragma GCC qwq("-fcrossjumping")
#pragma GCC qwq("-fthread-jumps")
#pragma GCC qwq("-funroll-loops")
#pragma GCC qwq("-fwhole-program")
#pragma GCC qwq("-freorder-blocks")
#pragma GCC qwq("-fschedule-insns")
#pragma GCC qwq("inline-functions")
#pragma GCC qwq("-ftree-tail-merge")
#pragma GCC qwq("-fschedule-insns2")
#pragma GCC qwq("-fstrict-aliasing")
#pragma GCC qwq("-fstrict-overflow")
#pragma GCC qwq("-falign-functions")
#pragma GCC qwq("-fcse-skip-blocks")
#pragma GCC qwq("-fcse-follow-jumps")
#pragma GCC qwq("-fsched-interblock")
#pragma GCC qwq("-fpartial-inlining")
#pragma GCC qwq("no-stack-protector")
#pragma GCC qwq("-freorder-functions")
#pragma GCC qwq("-findirect-inlining")
#pragma GCC qwq("-fhoist-adjacent-loads")
#pragma GCC qwq("-frerun-cse-after-loop")
#pragma GCC qwq("inline-small-functions")
#pragma GCC qwq("-finline-small-functions")
#pragma GCC qwq("-ftree-switch-conversion")
#pragma GCC qwq("-fqwq-sibling-calls")
#pragma GCC qwq("-fexpensive-optimizations")
#pragma GCC qwq("-funsafe-loop-optimizations")
#pragma GCC qwq("inline-functions-called-once")
#pragma GCC qwq("-fdelete-null-pointer-checks")
#include <iostream>
#include <cstdio>#define lb(x) (x & (-x))using namespace std;typedef long long LL;const LL N = 100010;LL n, m;
LL a[N];
LL t1[N], t2[N];char *p1, *p2, buf[N];
#define nc() (p1 == p2 && (p2 = (p1 = buf) +\
fread(buf, 1, N, stdin), p1 == p2) ? EOF : *p1 ++ )
LL read()
{LL x = 0, f = 1;char ch = nc();while (ch < 48 || ch > 57){if (ch == '-') f = -1;ch = nc();}while (ch >= 48 && ch <= 57)x = (x << 3) + (x << 1) + (ch ^ 48), ch = nc();return x * f;
}char obuf[N], *p3 = obuf;
#define putchar(x) (p3 - obuf < N) ? (*p3 ++ = x) :\
(fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3 ++ = x)
inline void write(LL x)
{if (!x){putchar('0');return;}LL len = 0, k1 = x, c[40];if (k1 < 0) k1 = -k1, putchar('-');while (k1) c[len ++ ] = k1 % 10 ^ 48, k1 /= 10;while (len -- ) putchar(c[len]);
}inline void add(LL t[], LL x, LL c)
{for (LL i = x; i <= n; i += lb(i))t[i] += c;
}inline LL sum(LL t[], LL x)
{LL res = 0;for (LL i = x; i; i -= lb(i))res += t[i];return res;
}inline LL psum(LL x)
{return sum(t1, x) * (x + 1) - sum(t2, x);
}int main()
{n = read(), m = read();for (LL i = 1; i <= n; i ++ ) a[i] = read();for (LL i = 1; i <= n; i ++ ){LL b = a[i] - a[i - 1];add(t1, i, b);add(t2, i, b * i);}LL op, l, r, d;while (m -- ){op = read(), l = read(), r = read();if (op == 2) write(psum(r) - psum(l - 1)), putchar(10);else{d = read();add(t1, l, d), add(t2, l, l * d);add(t1, r + 1, -d), add(t2, r + 1, -d * (r + 1));}}fwrite(obuf, p3 - obuf, 1, stdout);return 0;
}

End

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

苏州OV泛域名RSA加密算法https

RSA加密算法是一种非对称加密算法&#xff0c;它被广泛应用于信息安全领域。与对称加密算法不同&#xff0c;RSA加密算法使用了两个密钥&#xff0c;一个公钥和一个私钥。公钥可以公开&#xff0c;任何人都可以使用它加密信息&#xff0c;但只有私钥的持有者才能解密信息。RSA加…

探索美颜SDK技术:实现精准人脸美化的算法与挑战

在现代社交媒体和直播平台的兴起中&#xff0c;美颜技术已成为一种不可或缺的元素&#xff0c;让用户能够在镜头前展现出最佳的自己。这种技术的背后有着复杂而精密的算法&#xff0c;由美颜SDK驱动&#xff0c;以实现精准人脸美化。本文将探讨这些算法的核心原理、应用领域以及…

Multimap用法详解

Multimap Multimap 是 Google 的 Guava 库为 Java 引入的一种新集合类型&#xff0c;它允许将多个值存储在单个键下。它被设计为一种替代 Map<K, List> 或 Map<K, Set>&#xff08;JDK 标准集合框架&#xff09;的方案。 Multimap<K, V> 扩展了 AbstractMul…

【金融量化】Python实现根据收益率计算累计收益率并可视化

1 理论 理财产品&#xff08;本金100元&#xff09; 第1天&#xff1a;3% &#xff1a;&#xff08;13%&#xff09; ✖ 100 103 第2天&#xff1a;2% &#xff1a;&#xff08;12%&#xff09;✖ 以上 103 2.06 第3天&#xff1a;5% : &#xff08;15%&#xff09;✖ 以上…

SpringBoot中间件使用之EventBus、Metric、CommandLineRunner

1、EventBus 使用EventBus 事件总线的方式可以实现消息的发布/订阅功能&#xff0c;EventBus是一个轻量级的消息服务组件&#xff0c;适用于Android和Java。 // 1.注册事件通过 EventBus.getDefault().register(); // 2.发布事件 EventBus.getDefault().post(“事件内容”); …

【Linux】带你深入了解多路转接

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录 &#x1f449;多路转接…

竞赛项目 深度学习花卉识别 - python 机器视觉 opencv

文章目录 0 前言1 项目背景2 花卉识别的基本原理3 算法实现3.1 预处理3.2 特征提取和选择3.3 分类器设计和决策3.4 卷积神经网络基本原理 4 算法实现4.1 花卉图像数据4.2 模块组成 5 项目执行结果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &a…

Maven 生成(打包)带有依赖的可以直接执行的一个 jar 包

在pom中增加如下内容 <build><plugins><plugin><artifactId>maven-assembly-plugin</artifactId><configuration><archive><manifest><mainClass>com.example.xxx.YourClass</mainClass></manifest></…

Windows和Linux系统上的矢量运算:指令级并行计算SIMD(SSE/AVX)应用细节以及相关跨平台的源码解释

注&#xff1a;本文的SIMD&#xff0c;指的是CPU(base intel x86 architecture)指令架构中的相关概念。不涉及GPU端的算力机制。下面的代码在Win10和Linux上均可用。 基本概念 SSE: Streaming SIMD Extensions, x86 architecture AVX: Advanced Vector Extensions SIMD&#…

Springboot 多数据源 dynamic-datasource动态添加移除数据源

0.前言 上一篇文章我们讲了如何通过多数据源组件&#xff0c;在Spring boot Druid 连接池项目中配置多数据源&#xff0c;并且通过DS注解的方式切换数据源&#xff0c;《Spring Boot 配置多数据源【最简单的方式】》。但是在多租户的业务场景中&#xff0c;我们通常需要手动的…

RabbitMQ 消息队列

文章目录 &#x1f370;有几个原因可以解释为什么要选择 RabbitMQ&#xff1a;&#x1f969;mq之间的对比&#x1f33d;RabbitMQ vs Apache Kafka&#x1f33d;RabbitMQ vs ActiveMQ&#x1f33d;RabbitMQ vs RocketMQ&#x1f33d;RabbitMQ vs Redis &#x1f969;linux docke…

C语言案例 球落地反弹-10

题目&#xff1a;一球从100米高度自由落下&#xff0c;每次落地后反跳回原高度的一半;再落下&#xff0c;求它在第10次落地时&#xff0c;共经过多少米第10次反弹多高&#xff1f; 程序分析 球在落地后会反弹为原高度的一半&#xff0c;若设高度为h&#xff0c;那么每次落地的…

MATLAB程序初始化OpenFOAM颗粒位置

问题引入 在OpenFOAM的颗粒两相流求解器中&#xff0c;我们可以采用manualInjection的方式进行自定义颗粒的初始位置&#xff0c;这个命令十分方便&#xff0c;在CFDEM中也有类似的命令&#xff0c;不过CFDEM中的命令更加强大&#xff0c;我们不仅可以定义颗粒的初始位置&…

【我的2023秋招记录】溯流

我的2023秋招记录 开篇&#xff08;2023-08-11&#xff09; 2023已经过去大半年了&#xff0c;久违地打开CSDN&#xff0c;发现上一篇博客还停留在2022年的10月。那时候正值疫情严重&#xff0c;研究所回不去&#xff0c;整天呆在家里面摆烂摸鱼&#xff0c;也时常忧虑之后的…

pikachu中RCE出现乱码的解决的方案

exec “ping” 输入127.0.0.1 这种乱码的解决办法就是在pikachu/vul/rce/rce_ping.php目录里面的第18行代码 header("Content-type:text/html; charsetgbk");的注释打开即可。 BUT但是吧&#xff01;又出现了其他的乱码&#xff01;但是搞完这个再把它给注释掉就行了…

mac ssh连接另一台window虚拟机vm

vmware配置端口映射 编辑(E) > 虚拟网络编辑器(N)... > NAT设置(S)... window防火墙&#xff0c;入站规则添加5555端口 控制面板 > 系统和安全 > Windows 防火墙>高级设置>入站规则>新建规则... tips windows查看端口命令&#xff1a;netstat -ano | f…

vscode的ros拓展(插件)无法渲染urdf

文章目录 事件背景资料调查解决方案 事件背景 之前在vscode中一直用得好好的urdf预览功能&#xff0c;突然在某一天&#xff0c;不行了。 执行 URDF Preview之后&#xff0c;虽然弹出了一个URDF Preview的窗口&#xff0c;但是这个窗口里面啥都没有。没有网格、没有模型。 一开…

【前端】WeUI DatePicker时间组件绑定方法以及chatGPT回答

2023年&#xff0c;第33周&#xff0c;第1篇文章。给自己一个目标&#xff0c;然后坚持总会有收货&#xff0c;不信你试试&#xff01; WeUI DatePicker&#xff0c;这个组件在纯html静态文件js里用的比较少&#xff0c;也忘记默认绑定值怎么设置&#xff0c;就用chatGPT来找答…

Spring整合MyBatis(详细步骤)

Spring与Mybatis的整合&#xff0c;大体需要做两件事&#xff0c; 第一件事是:Spring要管理MyBatis中的SqlSessionFactory 第二件事是:Spring要管理Mapper接口的扫描 具体的步骤为: 步骤1:项目中导入整合需要的jar包 <dependency><!--Spring操作数据库需要该jar包…

LeetCode150道面试经典题--最后一个单词的长度(简单)

1.题目 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 2.示例 3.思路 通过对字符串的反转&#xff0c;转为数组开始遍历&#xff0c…