数据结构(2)树状数组

news2024/11/23 8:24:55

活动 - AcWing

参考:《算法竞赛进阶指南》-lyd

目录

一、概念

1.主要功能

2.实现方式

3.

二、例题

1.树状数组和逆序对

2.树状数组和差分

3. 两层差分

4. 结合二分


 

一、概念

1.主要功能

树状数组可以完成的功能主要有:

  • 维护序列的前缀和
  • 单点修改

上述两个操作在树状数组中都是logn级别的。对比前缀和数组和原始序列

前缀和数组查询前缀和o(1),但是单点修改o(n)。原始数组求前缀和o(n),单点修改o(1)。所以在大量的查询和修改操作并存的情况下,树状数组表现更优秀。

2.实现方式

注意下标从1开始

对一个较大的连续线性范围进行统计时,我们把它按照2的整数次幂分成若干个小范围进行预处理和计算。若一个正整数x的二进制表示为a_{k-1}a_{k-2}...a_2a_{1}a_{0},设其中等于1的位分别是\left \{ a_{i_1},a_{i_2},a_{i_3},...,a_{i_m} \right \}则正整数x可以被“二进制分解成”:

x=2^{i_1}+2^{i_2}+...+2^{i_m}

设i1>i2>...>im,则区间[1,x]可被划分为logx个小区间:

  1. 长度为2^i1的区间[1,2^i1]
  2. [2^i1 + 1,2^i1 + 2^i2]
  3. [2^i1 + 2^i2 + 1,2^i1 + 2^i2 + 2^i3]
  4. ......
  5. [2^i1 + 2^i2 + 2^i3+...+ 2^im-1 + 1,2^i1 + 2^i2 + 2^i3+...+ 2^im]

他们的特点是:若区间结尾为R,则区间长度等于lowbit(R)

树状数组就是基于上述思想的数据结构。对于原始序列a,我们建立树状数组tr,存储划分后每个区间的和。即:

tr[x]=\sum_{i=x-lobit(x+1)}^{x} a[i]

那么求前缀和可转化为

for(int i=x;i<=n;i+=lowbit(i)) res+=tr[x]
return res

有logn个区间,所以复杂度logn。

接下来考虑怎么构建这样的树状数组:

画一下经典1-16的区间图。很容易发现之中的树状关系。接下来我们找一下子节点和父节点的下标的关系。

对于一个子节点,父节点编号y和子节点编号x有关系:y=x+lowbit(x)

对父节点,子节点编号为

for(;x;x-=lowbit(x)) 

3.

对于查询操作:遍历所有子节点u1=x,u2=u1-lowbit(u1),.....un=1

对于修改操作,设单点x增加d

则把所有相关的父节点更新。父节点编号u1=x,u2=u1+lowbit(u1)......un=N

二、例题

1.树状数组和逆序对

 dp思想:将所有V和A按中间顶点位置划分。

对于顶点i,其V数量为在它左边比他大的数的个数  乘以  在它右边比他大的数的个数

A数量反之。

因此我们需要处理:在遍历到第i个点前,比y(i)大的数的个数和比y(i)小的数的个数

对此,我们应该记录,在遍历到i点前,每个y(x)出现的次数

这样,比yi大的数的个数为sum(n)-sum(y),比yi小的数的个数为sum(y)

处理完之后add(yi,1)即可

因此需要维护一个查询前缀和,支持单点修改的数组,并且复杂度不能n方,树状数组符合要求。

V A 数量 正反遍历一遍即可

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

const int N =2e5+10;

typedef long long LL;

int n;
int a[N];
int tr[N];//树状数组的原数组是 数字i出现的次数。
int Greater[N];
int Lower[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()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];

    for(int i=1;i<=n;i++)
    {
        int y=a[i];
        Lower[i]=sum(y-1);
        Greater[i]=sum(n)-sum(y);
        add(y,1);
    }
    memset(tr,0,sizeof tr);
    LL resV=0,resA=0;
    for(int i=n;i;i--)
    {
        int y=a[i];
        resV+=(LL)Greater[i]*(sum(n)-sum(y));
        resA+=(LL)Lower[i]*(sum(y-1));
        add(y,1);
    }
    cout<<resV<<" "<<resA<<endl;
    return 0;
}


作者:yankai
链接:https://www.acwing.com/activity/content/code/content/5174438/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2.树状数组和差分

 题目要求:区间增加,单点查询。

用树状数组维护原数组的差分数组,即可把条件转化为单点增加,区间查询。符合要求

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

typedef long long LL;
const int N =1e5+10;

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;
}

LL sum(int x)
{
    LL res=0;
    for(;x;x-=lowbit(x)) res+=tr[x];
    return res;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        add(i,a[i]-a[i-1]);
    }
    while(m--)
    {
        char op[2];
        int l,r,d;
        scanf("%s%d",op,&l);
        if(*op=='C')
        {
            scanf("%d%d",&r,&d);
            add(l,d),add(r+1,-d);
        }
        else
        {
            cout<<sum(l)<<endl;
        }
    }
    return 0;
}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/5174626/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3. 两层差分

 题目要求:区间增加,区间查询。

 推导一下:可知a1=b1,a2=b1+b2。。。。写成三角矩阵,补齐右上角

可推导出:


\sum_{i=1}^{x}\sum_{j=1}^{i} b[j]=(1+x)*\sum_{i=1}^{x}b[i]-\sum_{i=1}^x ib[i]
 

因此维护一个a的差分数组b,再维护一个i*ai的差分数组即可。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#include<cstdio>

typedef long long LL;
const int N =1e5+10;


int n,m;
LL tr1[N];
LL tr2[N];
int a[N];

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

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

LL sum(LL tr[],int x)
{
    LL res=0;
    for(;x;x-=lowbit(x)) res+=tr[x];
    return res;
}

LL presum(int x)
{
    return (x+1)*sum(tr1,x)-sum(tr2,x);
}


int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        int b=a[i]-a[i-1];
        add(tr1,i,b);
        add(tr2,i,(LL)b*i);
    }

    while(m--)
    {
        char op[2];
        int l,r,d;
        scanf("%s%d%d",op,&l,&r);
        if(*op=='C')
        {
            scanf("%d",&d);
            add(tr1,l,d),add(tr1,r+1,-d);
            add(tr2,l,l*d),add(tr2,r+1,-(r+1)*d);
        }
        else
        {
            cout<<presum(r)-presum(l-1)<<endl;
        }
    }
    return 0;
}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/5174858/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4. 结合二分

只知道前面有几头比第i头低。

因此我们从后往前推导,由于身高各不相同,设:备选序列1~n,

我们直接可以知道第n头就是第ai+1高的,即ai+1,则往前推,第n-1头就是备选序列去掉ai+1之后,剩下第a(n-1)+1高的。

因此我们需要的操作是:快速找出序列中第k高的,转换一下,备选序列可选即1,已被选为0,则操作变为快速找出一个x,使得sum(x)==k。因此树状数组可做,找出这个x可用二分查找

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=1e5+10;
int n;
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(;x;x-=lowbit(x)) res+=tr[x];
    return res;
}


bool check(int mid,int k)
{
    return sum(mid)>=k;
}


int main()
{
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        cin>>a[i];
    }

    for(int i=1;i<=n;i++)
        add(i,1);


    for(int i=n;i>=1;i--)
    {
        int k=a[i];
        k++;
        int l=1,r=n;
        while(l<r)
        {
            int mid=l+r>>1;
            if(check(mid,k))  r=mid;
            else l=mid+1;
        }
        a[i]=l;
        add(l,-1);
    }
    for(int i=1;i<=n;i++) cout<<a[i]<<endl;
    return 0;

}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/5175077/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

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

相关文章

pytest-pytest插件之测试覆盖率pytest-cov

简介 测试覆盖率是指项目代码被测试用例覆盖的百分比&#xff0c;使用pytest-cov插件可以统计测试覆盖率 添加链接描述 安装插件pytest-cov pip install pytest-cov用法 基本用法 –cov的参数是要统计代码覆盖率的源码&#xff0c;我将源码放在mysrc中&#xff0c;test_s…

qiankun微应用加载第三方js跨域报错

当我们在qiankun微应用&#xff0c;引入第三方js脚本时会产生跨域问题并报错&#xff0c;看qiankun的解释&#xff1a;常见问题 - qiankunqiankun会把静态资源的加载拦截&#xff0c;改用fetch方式获取资源&#xff0c;所以要求这些资源支持跨域。虽然qiankun也提供了解决方案&…

react面试题--react入门小案例案例

React入门应该是这样的 源码&#xff1a;https://github.com/dansoncut/React-beginner-tutorial-TeacherEgg.git 视频地址&#xff1a;https://www.bilibili.com/video/BV1be411w7iF/?spm_id_from333.337.search-card.all.click&vd_sourceae42119b44d398cd8fe181740c3e…

Java线程的六种状态

前言&#xff1a;其实线程的状态在操作系统的PCB中就对其进行了描述&#xff0c;但是Java中觉得自带的状态并不是特别好&#xff0c;于是引入了线程在Java中的六种状态。 (1) NEW 安排了工作还未行动&#xff0c;即&#xff1a;Thread对象创建出来了&#xff0c;但是内核的PCB…

开源工具 tomcat

Tomcat 封装了很多HTTP的操作&#xff1a;负责解析 HTTP 协议&#xff0c;解析请求数据&#xff0c;并发送响应数据。 官网 download下的which version&#xff1a; Apache Tomcat - Which Version Do I Want? 可以看tomcat对jdk的版本要求。 启动 启动&#xff1a;双击…

【redis6】第六章(新数据类型)

Bitmaps 简介 现代计算机用二进制&#xff08;位&#xff09;作为信息的基础单位&#xff0c; 1个字节等于8位&#xff0c; 例如“abc”字符串是由3个字节组成&#xff0c; 但实际在计算机存储时将其用二进制表示&#xff0c; “abc”分别对应的ASCII码分别是97、 98、 99&am…

SEO优化收徒站外引蜘蛛软件方法

SEO优化收徒站外引蜘蛛软件方法 今天我们讲解站外引蜘蛛的方法&#xff0c;站外引蜘蛛的方法无非就是五个大点。 第一个是搜索引擎的提交&#xff0c;我们通过是百度资源站展或者 360 或者神马头条&#xff0c;搜狗 bin 等等这样的一个搜索引擎去提交我们的链接。 里面主要是…

【css】结构选择器

结构选择器&#xff0c;也称之为组合器选择器&#xff0c;根据它们之间的特定关系来选取元素。CSS 中有四种不同的组合器&#xff1a;后代选择器 (空格)子选择器 (>)相邻兄弟选择器 ()通用兄弟选择器 (~)选择器示例描述element elementdiv p选择 div 元素内部的所有 p 元素e…

仗剑走天涯是梦想,仗键走天涯是坚持

在这信息化、数字化浪潮发展中&#xff0c;人们办公、娱乐、学习、生活都离不开了手机电脑平板等一系列电子设备&#xff0c;互联网行业工作者更是不可避免的需要频繁接触到电脑、键盘、鼠标等设备&#xff0c;今天给大家推荐一款性价比极高的键盘Keychron K3 Pro 一、keychron…

小程序API Promise化

一、 应用场景 小程序页面初始化时&#xff0c;需要去服务端获取token&#xff0c;所带参数在另外两个接口请求中&#xff0c;所写代码可能是这样子的&#xff1a; onLoad(options) {this.getToken() }, getToken() {wx.request({url: 后端API地址1,success: (res) > {//…

_Linux多线程-线程互斥篇

文章目录1. 进程线程间的互斥相关背景概念2. 互斥量mutex3. 互斥量的接口初始化互斥量销毁互斥量互斥量加锁和解锁4. 互斥量---锁静态分配&#xff08;初始化&#xff09;动态分配&#xff08;初始化&#xff09;5. 互斥量实现原理探究6. 总结&#xff1a;1. 进程线程间的互斥相…

【随即森林模型】

随机森林模型的基本原理和代码实现 集成模型简介 集成学习模型是机器学习非常重要的一部分。 集成学习是使用一系列的弱学习器&#xff08;或称之为基础模型&#xff09;进行学习&#xff0c;并将各个弱学习器的结果进行整合从而获得比单个学习器更好的学习效果的一种机器学习…

嵌入式设备中可以使用SQLite3吗?

摘要&#xff1a;数据库是用来存储和管理数据的专用软件&#xff0c;使得管理数据更加安全&#xff0c;方便和高效。数据库对数据的管理的基本单位是表(table)&#xff0c;在嵌入式linux中有时候它也需要用到数据库&#xff0c;听起来好难&#xff0c;其实就是几个函数&#xf…

论文精读:Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields ∗

姿态估计openpose系列算法解读 姿态估计任务 姿态估计任务首先需要检测出人体的各个关键点,将人体关键点进行拼接。 任务的困难有,首先,对于关键点检测任务,需要处理遮挡的问题,在拼接的过程中,需要处理多人的情况,即不能将不同人的关键点进行拼接。 标注数据信息 COCO…

linux系统中利用QT实现音乐播放器的功能

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何使用QT中的音乐播放器的功能与方法。 目录 第一&#xff1a;音乐播放器基本简介 第二&#xff1a;应用具体代码实现 第三&#xff1a;在源代码mainwindow.cpp中的实现 第四&#xff1a;程序运行效果 第一&#xff…

1.1计算机工作过程(超详细)

文章目录一、计算机组成框图二、思维导图三、部件剖析&#xff08;1&#xff09;存储器&#xff08;2&#xff09;运算器&#xff08;3&#xff09;控制器四、案例剖析&#xff08;重点&#xff09;&#xff08;1&#xff09;a2&#xff08;2&#xff09;a*b&#xff08;3&…

关于 国产麒麟系统上长时间运行Qt程序.xsession-erros文件占满磁盘导致无法写入 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/128660728 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

[强网杯 2019]随便注

目录 信息收集 方法一&#xff1a;堆叠注入 方法二&#xff1a;MySQL预处理 语法 payload 方法三&#xff1a;handler 知识点 语法 payload 信息收集 1 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version f…

开发中常用的Spring注解

一.IOC容器 Configuration ConpoentScan CompoentScans Bean Import DependsOn Lazy Compoent Repository Service Controller Autowired Qualifier 二.AOP切面 Aspect Pointcut Before After AfterReturning AfterThrowing Around 三.事务声明 Transac…

nacos一:服务注册

为什么用nacos: Eureka需要自己搭建项目&#xff0c;nacos下载后&#xff0c;就可以直接访问web界面,自带负载均衡 Nacos可以 1替代eureka做服务注册中心 2替代Config做服务配置中心 使用 一&#xff1a; 1 下载nacos,在bin目录下打开cmd窗口&#xff0c;输入startup.cmd -m s…