树状数组及应用

news2024/11/17 8:19:14

目录

1.树状数组的概念与基本编码

1.1.引导

1.2.lowbit(x)

1.3.树状数组的编码

2.树状数组的基本应用

2.1.单点修改+区间查询

2.2.区间修改+单点查询

例题:

2.3.区间修改+区间查询

例题:


如果数列A是静态不变的,那么处理前缀和复杂度为O(n),查询为O(1),但如果序列是动态变化的,如改变其中一个元素,那么就需要重新计算前缀和,如果每次查询都有变化,那么复杂度会大幅度增加。

有两种数据结构可以高效的处理这个问题:树状数组与线段树。

1.树状数组的概念与基本编码

1.1.引导

如图所示,c[1] = A[1], c[2] = c[1] + A[2], c[3] = A[3], c[4] = c[2] + c[3] + A[4], ... , c[8] = c[4] + c[6] + c[7] + A[8]。

利用c数组可以高效的完成以下两个操作。

(1)查询,即求前缀和sum。
(2)维护,即元素a发生变化时,能以O(log_2(n))的高效率修改c[] 的值。

结论:

(1)查询过程是每次去掉二进制的最后一个1。例如,求sum[7]:

  1. sum[7] += c[7]
  2. 7的二进制是111,去掉最后一个1,得110,即c[6],所以sum[7] += c[6]
  3. 110,去掉最后一个1,得100,sum[7] += c[4]
  4. 100,去掉最后一个1就没有了

故sum[7] = c[7] + c[6] + c[4]

(2)维护的过程是每次在二进制最后的1上加1。例如,更新a[3]:

  1. 3的二进制是11,在最后一个1上加1,得100,所以修改c[4];
  2. 100,在最后一个1上加1,得1000,所以修改c[8];
  3. 继续修改c[16],c[32]...

树状数组的关键就是找到最后一个1。

1.2.lowbit(x)

lowbit(x) = x & (-x),功能为找到x的二进制数最后一个1。其原理是利用负数的补码,例如x = 6 = 000110,(-x)_{} 补 = 111010,那么x & (-x) = 10 = 2;

lowbit(x) 部分结果如下:

x

x的二进制

lowbit(x)

tree[x]数组

1

1

1

tree[1] = a1

2

10

2

tree[2] = a2 + a1

3

11

1

tree[3] = a3

4

100

4

tree[4] = a4 + a3+ a2+ a1

5

101

1

tree[5] = a5

6

110

2

tree[6] = a6 + a5

7

111

1

tree[7] = a7

令m = lowbit(x),tree[x]的值是把a_{x}和他前面共m个数相加的结果。

tree[]数组是通过lowbit计算出的树状数组,它能够以二分的复杂度存储一个数列的数据,具体的说,tree[x]存储的是区间[x - lowbit(x) + 1, x]的每个数的和。

1.3.树状数组的编码

下面给出单点修改+区间查询的代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) (x & (-x))
const int N = 1000;
int tree[N];
void update(int x, int d) {//单点修改,修改玄素a[x],a[x] = a[x] + d
    while (x <= N) {
        tree[x] += d;
        x += lowbit(x);
    }
}
ll sum(int x) {//查询前缀和:返回前缀和sum = a[1] + a[2] + .., + a[x]
    ll ans = 0;
    while (x > 0) {
        ans += tree[x];
        x -= lowbit(x);
    }
    return ans;
}
void solve() {
    int n;
    cin >> n;
    vector<ll> a(n + 1, 0);//a[0]不用
    memset(tree, 0, sizeof(tree));
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        update(i, a[i]);
    }
    //查询区间和:
    cout << "Old : sum([1,n-1]):" << sum(n - 1) - sum(0) << endl;
    //模拟一次修改,a[n-1] + 100
    update(n - 1, 100);
    cout << "New : sum([1,n-1]):" << sum(n - 1) - sum(0) << endl;
}
int main() {
    ios::sync_with_stdio;
    cin.tie(0);
    cout.tie(0);
    solve();
    return 0;
}
/*
输入:
10
4 5 6 7 8 9 10 11 12 13
输出:
Old : sum([1,n-1]):72
New : sum([1,n-1]):172
*/

此代码过程:

(1)初始化:先清空数组tree,然后读取a数组每一个元素,用update() 逐步处理这n个数,得到tree[]数组;
(2)求前缀和:用sum()计算,求和基于tree数组;
(3)单点修改:执行update()函数,修改数组tree[]。

2.树状数组的基本应用

2.1.单点修改+区间查询

1.1.3.树状数组的编码已经给出

2.2.区间修改+单点查询

利用差分是前缀和的逆运算来求解

例题:

两种解法:

第一种,单纯的差分数组

#include<bits/stdc++.h>
using namespace std;
int n, a, b, diff[100002];
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	while (cin >> n && n != 0) {
		for (int i = 0; i <= n; i++) {
			diff[i] = 0;
		}
		for (int i = 0; i < n; i++) {
			cin >> a >> b;
			diff[a] += 1;
			diff[b + 1] -= 1;
		}
		diff[1] += diff[0];
		cout << diff[1];
		for (int i = 2; i <= n; i++) {
			diff[i] += diff[i - 1];
			cout << ' ' << diff[i];
		}
	}
	return 0;
}

 第二种,利用树状数组

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) (x & (-x))
const int N = 100010;
int tree[N];
void update(int x, int d) {
    while (x <= N) {
        tree[x] += d;
        x += lowbit(x);
    }
}
ll sum(int x) {
    ll ans = 0;
    while (x > 0) {
        ans += tree[x];
        x -= lowbit(x);
    }
    return ans;
}
void solve() {
    int n;
    while (cin >> n && n != 0) {
        memset(tree, 0, sizeof(tree));
        for (int i = 1; i <= n; i++) {
            int L, R;
            cin >> L >> R;
            update(L, 1);
            update(R + 1, -1);
        }
        for (int i = 1; i <= n; i++) {
            if (i != n)cout << sum(i) << ' ';
            else cout << sum(i) << endl;
        }
    }
}
int main() {
    ios::sync_with_stdio;
    cin.tie(0);
    cout.tie(0);
    solve();
    return 0;
}

2.3.区间修改+区间查询

完成区间修改需要一个tree,区间查询也需要一个tree,所以可以利用两个tree达成此要求

a_{1}+a_{2}+...+a_{k - 1 }+a_{k}
=d_{1} + (d_{1}+d_{2}) +... + \sum_{i=1}^{k-1}d_{i}+\sum_{i=1}^{k}d_{i}
=k\sum_{i =1 }^{k}d_{i} - \sum_{i =1 }^{k}(i-1)d_{i}

所以可以分别处理d和(i-1)d两个数组的树状数组

例题:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) (x & (-x))
const int N = 100010;
ll tree1[N], tree2[N];
void update1(ll x, ll d) {
    while (x <= N) {
        tree1[x] += d;
        x += lowbit(x);
    }
}
ll sum1(ll x) {
    ll ans = 0;
    while (x > 0) {
        ans += tree1[x];
        x -= lowbit(x);
    }
    return ans;
}
void update2(ll x, ll d) {
    while (x <= N) {
        tree2[x] += d;
        x += lowbit(x);
    }
}
ll sum2(ll x) {
    ll ans = 0;
    while (x > 0) {
        ans += tree2[x];
        x -= lowbit(x);
    }
    return ans;
}
void solve() {
    ll n, m;
    memset(tree1, 0, sizeof(tree1));
    memset(tree2, 0, sizeof(tree2));
    cin >> n >> m;
    ll old = 0, a;
    for (int i = 1; i <= n; i++) {
        cin >> a;
        update1(i, a - old);
        update2(i, (i - 1) * (a - old));
        old = a;
    }
    while (m--) {
        ll q, L, R, d;
        cin >> q;
        if (q == 1) {
            cin >> L >> R >> d;
            update1(L, d);
            update1(R + 1, -d);
            update2(L, (L - 1) * d);
            update2(R + 1, -R * d);
        }
        else {
            cin >> L >> R;
            cout << R * sum1(R) - sum2(R) - (L - 1) * sum1(L - 1) + sum2(L - 1) << endl;
        }
    }
}
int main() {
    ios::sync_with_stdio;
    cin.tie(0);
    cout.tie(0);
    solve();
    return 0;
}

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

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

相关文章

并发学习25--多线程 ThreadPoolExecutor

类图&#xff1a;定义了一些重要的接口和实现类 线程池的几种状态&#xff1a; ThreadPoolExecutor构造方法 1.救急线程 线程池中会有核心线程和救急线程&#xff1b;救急线程数最大线程数-核心线程数。而救急线程会在阻塞队列已经占满的情况下&#xff0c;执行下一个即将要被…

RK3568-开启ptp服务

硬件支持 mac或者phy需要支持ptp驱动支持 CONFIG_PTP_1588_CLOCK=y虚拟机端:虚拟机只支持软件时间戳。 安装ptp服务:sudo apt-get install linuxptp 修改ptp服务:sudo vi /lib/systemd/system/ptp4l.service,修改为ens33网口,-S表示使用软件时间戳。forlinx@ubuntu:~$ s…

Redis修改开源协议,6大备胎重见天日

背景&#xff1a;Redis2018年以来修改了多次开源协议&#xff0c;以前是把一些高级功能收费&#xff0c;这次彻底怒了&#xff0c;把核心代码的协议修改为RSALv2和SSPL双重协议&#xff0c;这个修改对普通用户不受影响&#xff0c;是向所有云厂商开炮&#xff0c;以后云厂商将不…

迁移android studio 模拟器位置

android studio 初始位置是安装在c盘&#xff0c;若是要迁移需 1创建一个目标位置如我的F:/avd 2在系统环境变量里面设置新的地址 变量名&#xff1a;ANDROID_SDK_HOME 变量值&#xff1a;F:/avd 3最重要的是文件复制&#xff0c;将C盘里面avd的上层目录.android的目录整体…

C++ static静态变量详解

目录 觉得有用就给博主点个赞吧&#xff01;谢谢啦&#xff01;嘻嘻嘻 概念&#xff1a; 特性 如何计算一个类创建了多少个对象&#xff1f; 概念&#xff1a; 用static关键字修饰的成员变量叫做静态成员变量 在类内部&#xff0c;但是属于全局&#xff0c;不属于任意一个…

前端调用接口地址跨越问题,nginx配置处理

在nginx配置里面添加add_header如下&#xff1a; add_header Access-Control-Allow-Origin *; #add_header Access-Control-Allow-Origin http://localhost:8080 always; add_header Access-Control-Allow-Methods GET, POST, PUT, D…

JavaScript Uncaught ReferenceError: WScript is not defined

项目场景&#xff1a; 最近在Visual Studio 2019上编译libmodbus库&#xff0c;出现了很多问题&#xff0c;一一解决特此记录下来。 问题描述 首先就是configure.js文件的问题&#xff0c;它会生成两个很重要的头文件modbus_version.h和config.h&#xff0c;这两个头文件其中…

【Lazy ORM 框架学习】

Gitee 点赞关注不迷路 项目地址 快速入门 模块所属层级描述快照版本正式版本wu-database-lazy-lambdalambda针对不同数据源wu-database-lazy-orm-coreorm 核心orm核心处理wu-database-lazy-sqlsql核心处理成处理sql解析、sql执行、sql映射wu-elasticsearch-starterESESwu-hb…

【React】vite + react 项目,进行配置 eslint

安装与配置 eslint 1 安装 eslint @babel/eslint-parser2 初始化配置 eslint3 安装 vite-plugin-eslint4 配置 vite.config.js 文件5 修改 eslint 默认配置1 安装 eslint @babel/eslint-parser npm i -D eslint @babel/eslint-parser2 初始化配置 eslint npx eslint --init相…

Vmware虚拟机无法用root直连说明

Vmware虚拟机无法用root直连说明 背景目的SSH服务介绍无法连接检查配置 背景 今天在VM上新装了一套Centos-stream-9系统&#xff0c;网络适配器的连接方式采用的是桥接&#xff0c;安装好虚拟机后&#xff0c;在本地用ssh工具进行远程连接&#xff0c;ip、用户、密码均是成功的…

【正点原子FreeRTOS学习笔记】————(12)信号量

这里写目录标题 一、信号量的简介&#xff08;了解&#xff09;二、二值信号量&#xff08;熟悉&#xff09;三、二值信号量实验&#xff08;掌握&#xff09;四、计数型信号量&#xff08;熟悉&#xff09;五、计数型信号量实验&#xff08;掌握&#xff09;六、优先级翻转简介…

LInux|命令行参数|环境变量

LInux|命令行参数|环境变量 命令行参数main的参数之argc&#xff0c;argv几个小知识<font color#0099ff size 5 face"黑体">1.子进程默认能看到并访问父进程的数据<font color#4b0082 size 5 face"黑体">2.命令行创建的程序父进程都是bash 环…

iterm2下使用tmux如果左右分屏用鼠标选中文字跨越pane的问题

原来的样子 如图所示&#xff0c;我开了左右分屏的tmux&#xff0c;如果我指向选择右边的三行div&#xff0c;用鼠标选中之后&#xff0c;会发现把左边的内容也框选了。 解决办法 Edit -》 Selection Respects Soft Boundaries 勾选这个功能&#xff08;选择遵循软边界&#…

秒杀VLOOKUP函数,查找数字我只服SUMIF函数

一提到数据查询&#xff0c;相信很多人的第一反应就是使用Vlookup函数。但是今天我想跟大家分享另一种比较另类的数据查询方式&#xff0c;就是利用SUMIF函数&#xff0c;相较于Vlookup函数它更加的简单灵活、且不容易出错&#xff0c;下面我们就来学习下它的使用方法。 1、常…

win10开启了hyper-v,docker 启动还是报错 docker desktop windows hypervisor is not present

问题 在安装了docker windows版本后启动 docker报错docker desktop windows hypervisor is not present 解决措施 首先确认windows功能是否打开Hyper-v 勾选后重启&#xff0c;再次启动 启动后仍报这个错误&#xff0c;是Hyper-v没有设置成功 使用cmd禁用再启用 一.禁用h…

rust中常用cfg属性和cfg!宏的使用说明,实现不同系统的条件编译

cfg有两种使用方式&#xff0c;一种是属性&#xff1a; #[cfg()]&#xff0c;一种是宏&#xff1a;cfg! &#xff0c;这两个都是非常常用的功能。 #[cfg()]是 Rust 中的一个属性 用于根据配置条件来选择性地包含或排除代码。cfg 是 "configuration" 的缩写&#xf…

左手医生:医疗 AI 企业的云原生提效降本之路

相信这样的经历对很多人来说并不陌生&#xff1a;为了能到更好的医院治病&#xff0c;不惜路途遥远奔波到大城市&#xff1b;或者只是看个小病&#xff0c;也得排上半天长队。这些由于医疗资源分配不均导致的就医问题已是老生长谈。 云计算、人工智能、大数据等技术的发展和融…

微信小程序页面制作练习——制作一个九宫格导航图

要求&#xff1a; 代码实现&#xff1a; 先将所需要的资源图片存入我的image文件里面 模拟练习供参考&#xff0c;不建议这样存入image里&#xff0c;因为本地图片占内存太大&#xff0c;不能预览。 一、list.wxml里面搭建框架代码&#xff1a; <!--pages/list/list.wxml…

有关Kitchen-Rosenfeld角点检测的公式推导

第一次看到下面这个公式时,不太清楚怎么推导过来的 后面看了有关Kitchen-Rosenfeld的文章后,明白了 假设梯度的角度 θ \theta θ tan ⁡ θ = I y I x \tan \theta =\frac{I_y}{I_x} tanθ=Ix​Iy​​ 其中 I y I_y Iy​为y偏导, I x I_x Ix​为x偏导, I x x I_{xx} I…