0x42 树状数组

0x42 树状数组

若一个正整数 x x x的二进制表示为 a k − 1 a k − 2 . . . a 2 a 1 a 0 a_{k-1}a_{k-2}...a_2a_1a_0 ak1ak2...a2a1a0,其中等于1的位是 { a i 1 , a i 2 , . . . , a i m } \{a_{i_1},a_{i_2},...,a_{i_{m}}\} {ai1,ai2,...,aim},则正整数 x x x可以被“二进制分解”成:
x = 2 i 1 + 2 i 2 + . . . + 2 i m x=2^{i_1}+2^{i_2}+...+2^{i_m} x=2i1+2i2+...+2im
不妨设 i 1 > i 2 > . . . > i m i_1>i_2>...>i_m i1>i2>...>im,进一步地,区间 [ 1 , x ] [1,x] [1,x]可以分成 O ( l o g x ) O(logx) O(logx)个小区间:

1.长度为 2 i 1 2^{i_1} 2i1的小区间 [ 1 , 2 i 1 ] [1,2^{i_1}] [1,2i1]

2.长度为 2 i 2 2^{i_2} 2i2的小区间 [ 2 i 1 + 1 , 2 i 1 + 2 i 2 ] [2^{i_1}+1,2^{i_1}+2^{i_2}] [2i1+1,2i1+2i2]

3.长度为 2 i 3 2^{i_3} 2i3的小区间 [ 2 i 1 + 2 i 2 + 1 , 2 i 1 + 2 i 2 + 2 i 3 ] [2^{i_1}+2^{i_2}+1,2^{i_1}+2^{i_2}+2^{i_3}] [2i1+2i2+1,2i1+2i2+2i3]

……

m.长度为 2 i m 2^{i_m} 2im的小区间 [ 2 i 1 + 2 i 2 + . . . + 2 i m − 1 + 1 , 2 i 1 + 2 i 2 + . . . + 2 i m ] [2^{i_1}+2^{i_2}+...+2^{i_{m-1}}+1,2^{i_1}+2^{i_2}+...+2^{i_m}] [2i1+2i2+...+2im1+1,2i1+2i2+...+2im]

这些小区间的共同特点是:若区间结尾为 R R R,则区间长度就等于 R R R的“二进制分解”下最小的2的次幂,即 l o w b i t ( R ) lowbit(R) lowbit(R)。例如 x = 7 = 2 2 + 2 1 + 2 0 x=7=2^2+2^1+2^0 x=7=22+21+20,区间 [ 1 , 7 ] [1,7] [1,7]可以分成 [ 1 , 4 ] [1,4] [1,4] [ 5 , 6 ] [5,6] [5,6] [ 7 , 7 ] [7,7] [7,7]三个小区间,长度分别是 l o w b i t ( 4 ) = 4 lowbit(4)=4 lowbit(4)=4 l o w b i t ( 6 ) = 2 lowbit(6)=2 lowbit(6)=2 l o w b i t ( 7 ) = 1 lowbit(7)=1 lowbit(7)=1

树状数组(Binary Indexed Trees)就是一种基于上述思想的数据结构,其基本用途是维护序列的前缀和。对于给定的序列 a a a,我们建立一个数组 c c c,其中 c [ x ] c[x] c[x]保存序列 a a a的区间 [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [xlowbit(x)+1,x]中所有数的和,即 ∑ i = x − l o w b i t ( x ) + 1 x a [ i ] \sum_{i=x-lowbit(x)+1}^x a[i] i=xlowbit(x)+1xa[i]

事实上,数组 c c c可以看做一个如下图所示的树形结构,图中最下边一行是 N N N个叶子结点(N=16),代表数值 a [ 1 ∼ N ] a[1\sim N] a[1N]。该结构满足以下性质:

1.每个内部结点 c [ x ] c[x] c[x]保存以它为根的子树中所有叶节点的和。

2.每个内部结点 c [ x ] c[x] c[x]的子节点个数等于 l o w b i t ( x ) lowbit(x) lowbit(x)的位数。

3.除树根外,每个内部结点 c [ x ] c[x] c[x]的父节点是 c [ x + l o w b i t ( x ) ] c[x+lowbit(x)] c[x+lowbit(x)]

4.树的深度为 O ( l o g N ) O(logN) O(logN)

如果 N N N不是2的整次幂,那么树状数组就是一个具有同样性质的森林结构。

在这里插入图片描述

树状数组支持的基本操作有两个,第一个操作是查询前缀和,即序列 a a a 1 ∼ x 1\sim x 1x个数的和。按照我们刚才提出的方法,应该求出 x x x的二进制表示中每个等于1的位,把 [ 1 , x ] [1,x] [1,x]分成 O ( l o g N ) O(logN) O(logN)个小区间,而每个小区间的区间和都已经保存在数组 c c c中,可在 O ( l o g N ) O(logN) O(logN)的时间里查询前缀和:

int ask(int x)
{int ans=0;while(x){ans+=c[x];x-=x&(-x);}return ans;
}

如果查询序列 a a a的区间 [ l , r ] [l,r] [l,r]中所有数的和,只需要计算 a s k ( r ) − a s k ( l − 1 ) ask(r)-ask(l-1) ask(r)ask(l1)

树状数组支持的第二个基本操作是单点增加,意思是给序列中的一个数 a [ x ] a[x] a[x]加上 y y y,同时正确维护序列的前缀和。只有节点 c [ x ] c[x] c[x]及其所有祖先节点保存的“区间和”包含 a [ x ] a[x] a[x],而任意一个节点的祖先至多只有 l o g N logN logN个,我们逐一对它们的 c c c值进行更新即可。下面的代码在 O ( l o g N ) O(logN) O(logN)时间内执行单点增加操作:

void add(int x,int y)
{while(x<=N){c[x]+=y;x+=x&(-x);}
}

树状数组初始化,比较一般的方法是:直接建立一个全为0的数组 c c c,然后对每个位置 x x x执行 a d d ( x , a [ x ] ) add(x,a[x]) add(x,a[x]),时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)。通常采用这种初始化方法已经足够。

更高效的方法是:从小到大依次考虑每个位置 x x x,借助 l o w b i t lowbit lowbit运算扫描他的父节点累加求和。若采用这种方法,上面树形结构中每条边只会被遍历一次,时间复杂度为 O ( ∑ k = 1 l o g N k ∗ N / 2 k ) = O ( N ) O(\sum_{k=1}^{logN}k*N/2^k)=O(N) O(k=1logNkN/2k)=O(N)

void build()
{for(int i=1;i<=N;++i){c[i]+=a[i];int fa=i+(i&-i);if(fa<=N)c[fa]+=c[i];}
}

1.树状数组与逆序对

任意给定一个集合 a a a,如果用 t [ v a l ] t[val] t[val]保存数值 v a l val val在集合 a a a中出现的次数,那么数组 t t t上的区间和(即 ∑ i = 1 r t [ i ] \sum_{i=1}^rt[i] i=1rt[i]就表示集合 a a a中范围在 [ l , r ] [l,r] [l,r]内的数有多少个。

我们可以在集合 a a a的数值范围上建立一个树状数组,来维护 t t t的前缀和。这样即使在集合 a a a中插入或删除一个数,也可以高效地进行统计。

我们在0x05节中提到了逆序对问题以及使用归并排序的解法。对于一个序列 a a a,若 i < j i<j i<j a [ i ] > a [ j ] a[i]>a[j] a[i]>a[j],则称 a [ i ] a[i] a[i] a [ j ] a[j] a[j]构成逆序对。按照上述思路,利用树状数组也可以求出一个序列的逆序对个数:

1.在序列 a a a的数值范围上建立树状数组,初始化为全0。

2.倒序扫描给定的序列 a a a,对于每个数 a [ i ] a[i] a[i]

(1)在树状数组中查询前缀和 [ 1 , a [ i ] − 1 ] [1,a[i]-1] [1,a[i]1],累加到答案 a n s ans ans中。

(2)执行“单点增加”操作,即把位置 a [ i ] a[i] a[i]上的数加1(相当于 t [ a [ i ] ] t[a[i]] t[a[i]]++),同时正确维护 t t t的前缀和。这表示数值 a [ i ] a[i] a[i]又出现了1次。

3. a n s ans ans即为所求。

for(int i=n;i;--i)
{ans+=ask(a[i]-1);add(a[i],1);
}

时间复杂度为 O ( ( N + M ) l o g M ) O((N+M)logM) O((N+M)logM) M M M为数值范围的大小。

当数值范围较大时,当然可以先进行离散化,再用树状数组进行计算。不过因为离散化本身就要通过排序实现,所以在这种情况下就不如直接用归并排序来计算逆序对数了。

2.树状数组的扩展应用

区间增加

树状数组仅支持单点增加,需要做出一些转化来解决问题。

新建一个数组 b b b,起初为全零。在区间 [ l , r ] [l,r] [l,r]每个值增加 d d d,我们把它转化成以下两条指令:

1.把 b [ l ] b[l] b[l]加上 d d d

2.把 b [ r + 1 ] b[r+1] b[r+1]减去 d d d

执行完后我们考虑一下 b b b数组的前缀和( b [ 1 ∼ x ] b[1\sim x] b[1x]的和)的情况:

1.对于 1 ≤ x < l 1\leq x <l 1x<l,前缀和不变。

2.对于 l ≤ x ≤ r l\leq x \leq r lxr,前缀和增加了 d d d

3.对于 r < x ≤ N r<x\leq N r<xN,前缀和不变( l l l处加 d d d r + 1 r+1 r+1处减 d d d,抵消)。

我们发现, b b b数组的前缀和 b [ 1 ∼ x ] b[1\sim x] b[1x]就反映了指令对于 a [ x ] a[x] a[x]产生的影响。

于是我们可以用树状数组来维护数组 b b b前缀和(对 b b b只有单点增加操作)。因为各次操作之间具有可累加性,所以在树状数组上查询前缀和 b [ 1 ∼ x ] b[1\sim x] b[1x],就得出了到目前为止所有指令在 a [ x ] a[x] a[x]上增加的数值总和。再加上 a [ x ] a[x] a[x]的初始值,就得到了修改后 x x x位置上的值

在这里插入图片描述
在这里插入图片描述

查询区间的和

在区间增加的基础上我们查询区间的和。

b b b数组的前缀和 ∑ i = 1 x b [ i ] \sum_{i=1}^x b[i] i=1xb[i]就是经过这些指令后 a [ x ] a[x] a[x]增加的值。

那么序列 a a a的前缀和 a [ 1 ∼ x ] a[1\sim x] a[1x],整体增加的值就是:
∑ i = 1 x ∑ j = 1 i b [ j ] \sum_{i=1}^x \sum_{j=1}^i b[j] i=1xj=1ib[j]
上式可以改写成:
∑ i = 1 x ∑ j = 1 i b [ j ] = ∑ i = 1 x ( x − i + 1 ) ∗ b [ i ] = ( x + 1 ) ∑ i = 1 x b [ i ] − ∑ i = 1 x i ∗ b [ i ] \sum_{i=1}^x \sum_{j=1}^i b[j]=\sum_{i=1}^x (x-i+1)*b[i]=(x+1)\sum_{i=1}^x b[i]-\sum_{i=1}^x i*b[i] i=1xj=1ib[j]=i=1x(xi+1)b[i]=(x+1)i=1xb[i]i=1xib[i]
那么我们再增加一个树状数组,用于维护 i ∗ b [ i ] i*b[i] ib[i]的前缀和 ∑ i = 1 x i ∗ b [ i ] \sum_{i=1}^x i*b[i] i=1xib[i],上式就可以直接查询、计算了。

具体来说我们建立两个树状数组 c 0 c_0 c0 c 1 c_1 c1,起初全部赋值为零。对于每条指令“C l r d”,执行四个操作:

1.在树状数组 c 0 c_0 c0中,把位置 l l l上的数加 d d d

2.在树状数组 c 0 c_0 c0中,把位置 r + 1 r+1 r+1上的数减 d d d

3.在树状数组 c 1 c_1 c1中,把位置 l l l上的数加 l ∗ d l*d ld

4.在树状数组 c 1 c_1 c1中,把位置 r + 1 r+1 r+1上的数减 ( r + 1 ) ∗ d (r+1)*d (r+1)d

另外,我们建立数组 s u m sum sum存储序列 a a a的原始前缀和。对于每天指令“Q l r”,当然还是拆分成 1 ∼ r 1\sim r 1r 1 ∼ l − 1 1\sim l-1 1l1两个部分,二者相减。写成式子就是:
( s u m [ r ] + ( r + 1 ) ∗ a s k ( c 0 , r ) − a s k ( c 1 , r ) ) − ( s u m [ l − 1 ] + l ∗ a s k ( c 0 , l − 1 ) − a s k ( c 1 , l − 1 ) ) (sum[r]+(r+1)*ask(c_0,r)-ask(c_1,r))-(sum[l-1]+l*ask(c_0,l-1)-ask(c_1,l-1)) (sum[r]+(r+1)ask(c0,r)ask(c1,r))(sum[l1]+lask(c0,l1)ask(c1,l1))

typedef long long ll;
int N,Q,a,b,c;
char op;
ll arr[100005];
ll sum[100005];
ll tr1[100005];
ll tr2[100005];ll ask(ll tr[],int x)
{ll ans=0;while(x){ans+=tr[x];x-=x&(-x);}return ans;
}void add(ll tr[],int x,ll y)
{while(x<=N){tr[x]+=y;x+=x&(-x);}
}int main()
{scanf("%d%d",&N,&Q);for(int i=1;i<=N;++i){scanf("%lld",&arr[i]);sum[i]=sum[i-1]+arr[i];}while(Q--){cin>>op;if(op=='Q'){scanf("%d%d",&a,&b);ll ans=sum[b]-sum[a-1];ans+=(b+1)*ask(tr1,b)-ask(tr2,b)-a*ask(tr1,a-1)+ask(tr2,a-1);printf("%lld\n",ans);}else{scanf("%d%d%d",&a,&b,&c);add(tr1,a,c);add(tr1,b+1,-c);add(tr2,a,a*c);add(tr2,b+1,-(b+1)*c);}}return 0;
}

值得指出的是,为什么我们把 ∑ i = 1 x ( x − i + 1 ) ∗ b [ i ] \sum_{i=1}^x (x-i+1)*b[i] i=1x(xi+1)b[i]变成 ( x + 1 ) ∑ i = 1 x b [ i ] − ∑ i = 1 x i ∗ b [ i ] (x+1)\sum_{i=1}^x b[i]-\sum_{i=1}^x i*b[i] (x+1)i=1xb[i]i=1xib[i]进行统计?这里的 x x x是关于“前缀和 a [ 1 ∼ x ] a[1\sim x] a[1x]”这个询问的变量,而 i i i是在每次修改时影响的对象。

对于前者来说,求和式中每一项同时包含 x x x i i i,在修改时无法确定 ( x − i + 1 ) (x-i+1) (xi+1)的值,只能维护 b [ i ] b[i] b[i]的前缀和。在询问时需要面临一个“系数为等差数列”的求和式,计算起来非常困难。

对于后者来说,求和式中的每一项只与 i i i有关。它通过一次容斥,把 ( x + 1 ) (x+1) (x+1)提取为常量,使得 b [ i ] b[i] b[i]的前缀和与 i ∗ b [ i ] i*b[i] ib[i]的前缀和可以分别用树状数组进行维护。这种分离包含有多个变量的项,使公式中不同变量之间相互独立的思想非常重要,我们在下一章讨论动态规划时会多次用到。

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

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

相关文章

鸿蒙原生应用再添新丁!喜马拉雅入局鸿蒙

鸿蒙原生应用再添新丁&#xff01;喜马拉雅入局鸿蒙 来自 HarmonyOS 微博12月20日消息&#xff0c; #喜马拉雅正式完成鸿蒙原生应用版本适配#&#xff0c;作为音频业巨头的喜马拉雅 &#xff0c;将基于#HarmonyOS NEXT#创造更丰富、更智慧的全场景“声音宇宙”&#xff01;#鸿…

Python 正则表达式入门:轻松掌握字符串匹配的艺术

Python 正则表达式入门&#xff1a;轻松掌握字符串匹配的艺术 引言&#xff1a;什么是正则表达式&#xff1f;基础知识&#xff1a;正则表达式的语法和规则Python中的正则表达式&#xff1a;re模块的使用实战应用&#xff1a;常见的正则表达式案例最佳实践与常见错误结语&#…

格密码:LWE设计公钥密码系统

目录 一. LWE公私钥对 二. 怎么加密&#xff1f; 三. 怎么解密&#xff1f; 四. 正确性分析 五. 安全性 在格密码中&#xff0c;LWE(Learning With Errors)问题非常重要&#xff0c;本文章将介绍一些基于LWE设计的公钥密码方案&#xff0c;并详细讨论这些方案是如何运行的…

oracle怎样才算开启了内存大页?

oracle怎样才算开启了内存大页&#xff1f; 关键核查下面三点&#xff1a; 1./etc/sysctl.conf vm.nr_hugepages16384这是给了32G&#xff0c;计划sga给30G&#xff0c;一般需多分配2-4G sysctl -p生效 看cat /proc/meminfo|grep Huge啥结果&#xff1f; 这种明显是配了…

蓝牙物联网开发与应用:五大核心应用场景!

蓝牙技术在物联网中的五大核心应用场景 1、智能家居 通过蓝牙连接智能家居设备&#xff0c;如智能灯泡、智能插座、智能恒温器等&#xff0c;可以实现远程控制、语音控制等功能&#xff0c;提高家居的智能化程度和便利性。 2、智能穿戴设备 蓝牙技术可以连接智能手表、智能手…

01AVue入门(持续学习中)

1.使用AVue开发简单的前端页面直接简单到起飞,他是Element PlusVueVite开发的,不需要向元素的前端代码一样一个组件要传很多参数,他可以使用Json文本来控制我们要传入的数据结构来决定显示什么 //我使用的比较新,我们也可以使用cdn直接使用script标签直接引入 2.开发中遇到的坑…

共享目录搭建

【linux系统】 1.sudo yum install nfs-utils 或 sudo apt install nfs-common 问题&#xff1a;如果apt install nfs-common报错dpkg: error processing package rpcbind (--configure) 解决方法&#xff1a;删除所有信息之后update sudo mv/var/lib/dpkg/info/ /va…

鸿蒙ArkTS语言介绍与TS基础法

1、ArkTS介绍 ArkTS是HarmonyOS主力应用开发语言&#xff0c;它在TS基础上&#xff0c;匹配ArkUI框架&#xff0c;扩展了声明式UI、状态管理等响应的能力&#xff0c;让开发者以更简洁、更自然的方式开发跨端应用。 JS 是一种属于网络的高级脚本语言&#xff0c;已经被广泛用…

【ITK库学习】使用itk库进行图像配准:“Hello World”配准(一)

目录 1、itkImageRegistrationMethod / itkImageRegistrationMethodv42、itkTranslationTransform3、itkMeanSquaresImageToImageMetric / itkMeanSquaresImageToImageMetric44、itkRegularStepGradientDescentOptimizerv / itkRegularStepGradientDescentOptimizerv4 图像配准…

MyBatis的ORM!!!

首先你要明白为什么使用ORM&#xff1a;我们看一个示例&#xff0c;我们发现我们要声明的pojo类中的属性名和数据库中的字段名不一致&#xff0c;这时就需要我们使用MyBatis的ORM。 首先还是准备工作&#xff1a; 1.创建Maven工程&#xff0c;还没有配置Maven的和还不会的去看这…

2023年12月20日学习总结

今日to do list&#xff1a; 学习kaggle中store sales中的dart forcasting&#x1f3af; 大概搜集一个声纹识别的报告&#xff08;老师给的新项目&#x1f62d;&#xff09; 学习时不刷手机 okkkkkkkkkkkkkk 开始&#x1f44d; 1. 时间序列预测- a complete guide 总结一下这…

mysql创建用户和赋权

1.创建用户 CREATE USER new_userlocalhost IDENTIFIED BY user_password; “localhost"只允许本地连接&#xff0c;而”%"允许所有IP地址都可以连接到服务器。 2.赋权 GRANT ALL PRIVILEGES ON database_name.* TO new_userlocalhost; FLUSH PRIVILEGES; 3.给…

【C++初阶】学习string类的模拟实现

目录 前言&#xff1a;一、创建文件和类二、实现string类2.1 私有成员和构造函数2.2 析构函数2.3 拷贝构造函数2.3.1 写法12.3.2 写法2 2.4 赋值重载函数2.4.1 写法12.4.2 写法2 2.5 迭代器遍历访问2.6 下标遍历访问2.7 reserve2.8 resize2.9 判空和清理2.10 尾插2.10.1 尾插字…

计算机组成原理综合2

21、和外存储器相比&#xff0c;内存储器的特点是________。C A. 容量大、速度快、成本低 B. 容量大、速度慢、成本高 C. 容量小、速度快、成本高 D. 容量小、速度快、成本低 22、某计算机字长16位&#xff0c;存储器容量64KB&#xff0c;若按字编址&#xf…

diffusers-Inpainting

原文链接&#xff1a;添加链接描述 白色mask区域仅使用生成出来的&#xff0c;非白色mask区域使用原始影像&#xff0c;但是图像有点不平滑 import PIL import numpy as np import torchfrom diffusers import AutoPipelineForInpainting from diffusers.utils i…

Ubuntu 常用命令之 gzip 命令用法介绍

gzip 是一个在 Linux 和 Unix 系统中常用的文件压缩工具。它的名字来源于 GNU zip&#xff0c;作为一个自由软件&#xff0c;它是 GNU 项目的一部分。gzip 命令通常用于压缩文件&#xff0c;以节省磁盘空间&#xff0c;或者减小文件的大小&#xff0c;以便于网络传输。 gzip 命…

音视频直播核心技术介绍

直播流程 采集&#xff1a; 是视频直播开始的第一个环节&#xff0c;用户可以通过不同的终端采集视频&#xff0c;比如 iOS、Android、Mac、Windows 等。 前处理&#xff1a;主要就是美颜美型技术&#xff0c;以及还有加水印、模糊、去噪、滤镜等图像处理技术等等。 编码&#…

网络基础介绍

1.网线制作 1.1 网线制作需要的工具 网线 网线钳 水晶头 测试仪 ​编辑 1.2 网线的标准 1.3 网线的做法 2.集线器&交换机&路由器的介绍 3.OSI七层模型 4.路由器的设置 4.1 常见的路由器设置地址 4.2 常见的路由器账号密码 4.3 登录路由器 设置访客网…

管理类联考——数学——真题篇——按知识分类——代数——数列

【等差数列 ⟹ \Longrightarrow ⟹ 通项公式&#xff1a; a n a 1 ( n − 1 ) d a m ( n − m ) d n d a 1 − d A n B a_n a_1(n-1)d a_m(n-m)dnda_1-dAnB an​a1​(n−1)dam​(n−m)dnda1​−dAnB ⟹ \Longrightarrow ⟹ A d &#xff0c; B a 1 − d Ad&#x…

从零开始构建高效的网校平台:在线教育系统源码的开发指南

随着科技的不断发展&#xff0c;在线教育在现代社会中变得愈发重要。本文将为您提供一份详尽的指南&#xff0c;从零开始构建高效的网校平台&#xff0c;覆盖在线教育系统源码的关键开发步骤。 第一步&#xff1a;明确需求和目标 在开始之前&#xff0c;明确您的网校平台的需…