首页 > 其他分享 >树状数组讲解

树状数组讲解

时间:2023-07-23 14:23:02浏览次数:28  
标签:树状 int lowbit sum ans 数组 讲解

1.引入

给出2个问题:

问题1:

问题2:

 

数据范围:

很显然,用 朴素的方法去模拟会超时,那么就需要一些更优秀的数据结构

——树状数组

2.基本概念

 

给出一个数组 a[8]={1,2,3,4,5,6,7,8}

然后看图

 

在如图的一棵二叉树中,f[1]=a[1],f[3]=a[3],f[5]=a[5],f[7]=a[7]

f[2]=a[1]+a[2],f[6]=a[5]+a[6]

f[4]=a[1]+a[2]+a[3]+a[4]

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

3.用法

(1)区间查询

如果将前i个元素的和记为sum[1],那么通过这些值就可以把所有的sum[i]表示出来

比如:sum[7]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]=f[4]+f[6]+f[7]

这里要介绍一下lowbit,lowbit(x)表示将x转为二进制后从右向左第一个1及后面的数字

比如7=(111)2,那么lowbit(7)=001;6=(110)2,那么lowbit(6)=010

再给出一个结论,lowbit(x)=x&(-x),这里就不证明了,可以自己举例验证

然后我直接给出代码,结合代码理解

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

当x=7时,先ans+=f[7],这时lowbit(x)=(001)2,x=x-lowbit(x)=(110)2=6

然后ans+=f[6],这时lowbit(x)=(010)2,x=x-lowbit(x)=(100)2=4

最后ans+=f[4],这时lowbit(x)=(100)2,x=x-lowbit(x)=(000)2=0

循环结束

模拟代码可以得到,ans[7]=f[7]+f[6]+f[4]

然后就用前缀和的方法,区间[x,y]的和就是sum(y)-sum(x-1)

(2)单点修改

首先思考一个问题:当a[i]被改动时,有哪些f[]的值会变?

看那张图,可以发现当a[1]变时,f[1],f[2],f[4],f[8]会变;当a[3]变时,f[3],f[4],f[8]会变......

继续给出代码理解

void add(int x,int k)
{
    while(x<=n)
    {
        f[x]+=k;
        x+=lowbit(x);
    }
}

举x=3时的例子

先f[3]+=k,这时lowbit(x)=(001)2,x=x+lowbit(x)=(100)2=4

再f[4]+=k,这时lowbit(x)=(100)2,x=x+lowbit(x)=(1000)2=8

最后f[8]+=k,这时lowbit(x)=(1000)2,x=x+lowbit(x)=(10000)2=16

超出范围,循环结束

模拟代码可得,当a[3]变时,f[3],f[4],f[8]会变

其实基本框架已经讲完了......

(3)区间修改

前面两种操作都运用了前缀和,而接下来的两种操作就要用到差分

接下来的两种代码及思想都与之前有共同之处,所以解释相对简略

原数组每次读入时,减去前一个,再加到树状数组里,这样就差分了。

 

int la=0,a;
for(int i=1;i<=n;i++)
{
    cin>>a;
    add(i,a-la);
    la=a;
}
        

 

根据树状数组的性质,只需要前后节点都维护就行了。

用我的程序就是

void add(int x,int k)
{ while(x<=n)
  { f[x]+=k; x+=lowbit(x); } } add(x,k); add(y+1,-k);

(4)单点查询

 因为我们用的是差分,差分的前缀就是原数组,该节点的结束值就是此时差分数组的前缀

所以代码就是

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

cout<<ans(x)<<'\n';

4.关于树状数组

  • 查询和修改的复杂度均为O(log n)
  • 属于二进制的运用
  • 是解决逆序对问题的常用方法

5.推荐例题

首先是“引入”里给出的两道模板题

https://www.luogu.com.cn/problem/P3374

https://www.luogu.com.cn/problem/P3368

以及一道逆序对题目

https://www.luogu.com.cn/problem/P1908

标签:树状,int,lowbit,sum,ans,数组,讲解
From: https://www.cnblogs.com/wyh0721/p/17574794.html

相关文章

  • Unity3D 自定义类的数组初始化
    实现功能:1.自定义类,用于保存数据等2.初始化数组代码:publicclasstree_elem{//位置publicintx,y;//大小【相对于原始大小的比例】最后随机分配publicfloatsize;//树的类型,最后随机分配publictree_kindkind;publictree_ele......
  • 老杜 JavaWeb 讲解(十三) ——JSP简单了解
    (十四)JSP相关视频:35-JSP原理深度解析36-JSP的各种基础语法37-JSP的输出语法第一个JSP程序在WEB-INF目录之外创建一个index.jsp文件,然后这个文件中没有任何内容。将上面的项目部署之后,启动服务器,打开浏览器,访问以下地址:http://localhost:8080/jsp/index.jsp展现......
  • jquery数组添加数据
    如何使用jQuery添加数据到数组中简介在使用jQuery开发过程中,经常会遇到需要向数组中添加数据的情况。本文将介绍如何使用jQuery实现数组的添加操作,帮助刚入行的小白快速掌握这一技巧。整体流程下面是实现"jquery数组添加数据"的整体流程,我们将使用一个表格展示每一步需要做的事......
  • jquery数组 splice
    jQuery数组splice方法详解在JavaScript编程中,数组是一种常用的数据结构,用于存储一系列的数据元素。在处理数组的时候,我们常常需要对数组进行增加、删除或修改等操作。jQuery库提供了一个非常方便的方法,即splice()方法,用于实现对数组的增删改操作。本文将详细介绍splice()方法的使......
  • k8s基础之概念讲解
    目录1Kubernetes1.1简介1.2特性1.3架构1.4组件1.4.1MasterNode1.4.2WorkNode1.4.3service1.4.4Namespace1.4.5Volume1.5Pod控制器1.5.1pod1.5.2Pod控制器1Kubernetes1.1简介Kubernetes是一个全新的基于容器技术的分布式架构解决方案,是Google开源的一个容器......
  • Js数组
      Js数组方法  1.把数组转换为字符串:toString() join('分隔符')  2.pop() 删除数组最后一个元素  返回被删除的值  3.push() 在数组末尾添加一个元素 返回数组长度  4.shift() 删除数组首个元素 返回被删除的值  5.unshift() 在......
  • python 字符串 不在数组中
    如何判断一个字符串不在数组中引言本文将教会你如何判断一个字符串是否不在数组中。在Python中,我们可以使用循环结构和判断语句来完成这个任务。首先,我们来整理一下实现该功能的流程,然后逐步介绍每一步需要做什么,以及需要使用的代码和其注释。流程概述步骤描述步骤1......
  • python 数组是否存在值 对应key
    判断Python数组中是否存在某个值对应的键1.前言在Python中,数组是一种有序的、可变的数据类型,可以存储多个值。数组中的每个值都有对应的索引,就像一个字典中的键值对一样。在某些情况下,我们可能想要判断数组中是否存在某个值对应的键。本文将介绍如何使用Python来实现这个功能。......
  • python 数组保存到文件
    Python数组保存到文件的方法概述在Python中,我们可以使用多种方法将数组保存到文件中。本文将介绍一种简单而常用的方法,使用numpy库来实现。numpy是Python中用于科学计算的一个强大的库,它提供了高性能的多维数组对象以及用于处理这些数组的工具。接下来,我们将一步步指导你实现将P......
  • C++数组下标可以是负数
     inta[5]={0,1,2,3,4}int*p=a+4;cout<<p[-2]<<endl;//2p[-2]表示从指针当前位置向前寻址两个数据类型长度注1:只有在p是指针时才能这么做,不应当出现a[-2]这样数组名加负数下标的用法,因为会超出数组地址范围注2:一般不建议这么做,可能会出现各种寻址......