【算法 & 高级数据结构】树状数组:一种高效的数据结构(二)

这篇具有很好参考价值的文章主要介绍了【算法 & 高级数据结构】树状数组:一种高效的数据结构(二)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🚀个人主页:为梦而生~ 关注我一起学习吧!
💡专栏:算法题、 基础算法、数据结构~赶紧来学算法吧
💡往期推荐
【算法基础 & 数学】快速幂求逆元(逆元、扩展欧几里得定理、小费马定理)
【算法基础】深搜
数据结构各内部排序算法总结对比及动图演示(插入排序、冒泡和快速排序、选择排序、堆排序、归并排序和基数排序等)
【算法 & 高级数据结构】树状数组:一种高效的数据结构(一)

上一篇文章我们介绍了树状数组这个数据结构,并且进行了其原理的数学推导,这篇文章基于上一篇文章,来讲一下这个数据结构在算法题中的应用

还不知道树状数组是什么的来看这篇文章:【算法 & 高级数据结构】树状数组:一种高效的数据结构(一)

题目来源:AcWing



0 通用模板

首先,通用模板先摆上

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

1 单点修改,区间求和

  • 例题
    【算法 & 高级数据结构】树状数组:一种高效的数据结构(二),基础算法,数据结构,算法,数据结构,蓝桥杯

输入格式
第一行一个数 n n n

第二行是 n n n
个数,分别代表 y 1 , y 2 , … , y n y_1,y_2,…,y_n y1y2,,yn

输出格式
两个数,中间用空格隔开,依次为 V 的个数和 的个数。

数据范围
对于所有数据, n ≤ 200000 n≤200000 n200000,且输出答案不会超过 int64。
y 1 ∼ y n y_1∼y_n y1yn 1 1 1 n n n 的一个排列。

输入样例

5
1 5 3 2 4

输出样例

3 4
  • 主要思路

因为两个符号的特点对应的每个点的纵坐标有这样的特点: V 意味着两边点的纵坐标高于当前点的纵坐标, 意味着两边的纵坐标小于当前的纵坐标。所以我们只需要遍历两次数组,记录每个点左右高于或低于当前点的纵坐标的点的数量,然后利用排列组合,计算出总数。

以下题解来源:https://www.acwing.com/solution/content/13818/

  1. 从左向右依次遍历每个数a[i],使用树状数组统计在i位置之前所有比a[i]大的数的个数、以及比a[i]小的数的个数。
    统计完成后,将a[i]加入到树状数组。
  2. 从右向左依次遍历每个数a[i],使用树状数组统计在i位置之后所有比a[i]大的数的个数、以及比a[i]小的数的个数。
    统计完成后,将a[i]加入到树状数组。
  • 代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

typedef long long LL;

const int N = 200010;
int n;
int a[N], tr[N];
int Greater[N], 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++) scanf("%d", &a[i]);
    
    //将区间分成n份,看每一份左边有多少个大于它和小于它的
    for(int i = 1; i <= n; i++){
        int y = a[i];
        Greater[i] = sum(n) - sum(y);
        Lower[i] = sum(y - 1);
        add(y, 1);
    }
    
    //memset一下tr数组,倒着求每一份右边有多少个小于和大于它的
    memset(tr, 0, sizeof(tr));
    LL res1 = 0, res2 = 0;
    for(int i = n; i; i--){
        int y = a[i];
        res1 += Greater[i] * (LL)(sum(n) - sum(y));
        res2 += Lower[i] * (LL)sum(y - 1);
        add(y, 1);
    }
    
    cout << res1 << " " << res2 << endl;
    
    return 0;
}

2 区间增减,单点查询

主要思想:建立差分数组

  • 例题

【算法 & 高级数据结构】树状数组:一种高效的数据结构(二),基础算法,数据结构,算法,数据结构,蓝桥杯
数据范围
1 ≤ N , M ≤ 1 0 5 , 1≤N,M≤10^5, 1N,M105,
∣ d ∣ ≤ 10000 , |d|≤10000, d10000,
∣ A [ i ] ∣ ≤ 1 0 9 |A[i]|≤10^9 A[i]109

输入样例

10 5
1 2 3 4 5 6 7 8 9 10
Q 4
Q 1
Q 2
C 1 6 3
Q 2

输出样例

4
1
2
5
  • 主要思路

树状数组每次修改对应的是某个范围的前缀和的值的修改,所以如果需要在大数据量的情况下进行区间修改操作,大概率会TLE的。

但是我们想到,区间操作我们可以利用最基础的差分,使得区间操作优化成 O ( 1 ) O(1) O(1)的复杂度。

可以看到,将题目在原始组上的操作,转换到差分数组上,和树状数组解决的问题一致。

  • 代码
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N = 100010;
int tr[N], a[N];
int n, m;

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 quary(int x){
    LL res = 0;
    for(int i = x; i; i -= lowbit(i)) res += tr[i];
    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;
        cin >> op;
        
        if(op == 'C'){
            int l, r, d;
            cin >> l >> r >> d;
            add(l, d), add(r + 1, -d);
        }else{
            int x;
            cin >> x;
            cout << quary(x)<< endl;
        }
    }
    
    return 0;
}

3 区间增减,区间求和

主要思想:建立差分数组+公式

  • 例题

【算法 & 高级数据结构】树状数组:一种高效的数据结构(二),基础算法,数据结构,算法,数据结构,蓝桥杯
数据范围
1 ≤ N , M ≤ 1 0 5 , 1≤N,M≤10^5, 1N,M105,
∣ d ∣ ≤ 10000 , |d|≤10000, d10000,
∣ A [ i ] ∣ ≤ 1 0 9 |A[i]|≤10^9 A[i]109

输入样例

10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4

输出样例

4
55
9
15
  • 主要思想

上一个应用虽然利用差分数组解决了区间操作的问题,但是区间操作完之后,利用差分数组不利于进行区间查询,所以需要进行一些推导,看看有什么性质可以利用。

数学推导见如下题解:https://www.acwing.com/solution/content/44886/
因此只需维护两个树状数组即可
一个是差分数组d[i]的树状数组tr[i],还有一个是i*d[i]的树状数组tri[i]文章来源地址https://www.toymoban.com/news/detail-843478.html

  • 代码
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N = 100010;
int a[N];   
LL tr1[N], tr2[N];  //tr1[i] : b[i]的前缀和  tr2[i] : i * b[i]的前缀和
int n, m;

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 quary(LL tr[], int x){
    LL res = 0;
    for(int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

LL prefix_sum(int x){
    return quary(tr1, x) * (LL)(x + 1) - quary(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, (LL)b);
        add(tr2, i, (LL)i * b);
    }
    
    while(m--){
        char op;
        cin >> op;
        
        if(op == 'C'){
            int l, r, d;
            cin >> l >> r >> d;
            add(tr1, l, (LL)d), add(tr1, r + 1, (LL)-d);
            add(tr2, l, (LL)l * d), add(tr2, r + 1, (LL)(r + 1) * -d);
        }else{
            int l, r;
            cin >> l >> r;
            cout << prefix_sum(r) - prefix_sum(l - 1) << endl;
        }
    }
    
    
    return 0;
}

到了这里,关于【算法 & 高级数据结构】树状数组:一种高效的数据结构(二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【算法每日一练]-结构优化(保姆级教程 篇4 树状数组,线段树,分块模板篇)

    目录 分块 分块算法步骤: 树状数组 树状数组步骤: 线段树点更新 点更新步骤: 线段树区间更新 区间更新步骤: 不同于倍增和前缀和与差分序列。 前缀和处理不更新的区间和 差分处理离线的区间更新问题 倍增处理离线的区间最值问题 分块,树状数组,线段树: 分块处

    2024年02月04日
    浏览(32)
  • 数据结构的魔法:高级算法优化实战

    🎉欢迎来到数据结构学习专栏~数据结构的魔法:高级算法优化实战 ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:数据结构学习 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 数据结构学习 🍹文章作者技术和水平有

    2024年02月06日
    浏览(26)
  • 学习高级数据结构:探索平衡树与图的高级算法

    🎉欢迎来到数据结构学习专栏~学习高级数据结构:探索平衡树与图的高级算法 ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:数据结构学习 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 数据结构学习 🍹文章作者技

    2024年02月09日
    浏览(31)
  • 深入学习与探索:高级数据结构与复杂算法

    🎉欢迎来到数据结构学习专栏~深入学习与探索:高级数据结构与复杂算法 ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:数据结构学习 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 数据结构学习 🍹文章作者技术和

    2024年02月09日
    浏览(35)
  • 数据结构与算法(一): 稀疏数组

    在五子棋游戏或类似的游戏中,我们可以把整个棋盘想象成是一个有规律的二维数组,其值由0、1、2三个数字组成,0代表空白区域,1代表白子,2代表黑子。这种情况:即当一个数组中大部分元素为0或者为同一值时,存储该数组数据可以使用稀疏数组来对原始数组进行精简,

    2024年02月11日
    浏览(35)
  • 数据结构与算法 | 数组(Array)

    数组(Array)应该是最基础的数据结构之一,它由相同类型的元素组成的集合,并按照一定的顺序存储在内存中。每个元素都有一个唯一的索引,可以用于访问该元素。 数组索引(Index): 数组中的每个元素都有一个唯一的整数索引,从0开始计数。索引用于访问数组中的元素

    2024年02月08日
    浏览(35)
  • JavaScript数据结构与算法整理------数组

            数组的标准定义: 一个存储元素的线性集合,元素可以通过索引来任意存取,索引通常是数字,用来计算元素之间存储位置的偏移量 ,几乎所有的编程语言都有类似的数据结构,而JavaScript的数组略有不同。         JavaScript中的数组是一种特殊的对象,用来表示偏

    2023年04月24日
    浏览(46)
  • 【数据结构和算法】寻找数组的中心下标

    Java基础合集 数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 前缀和的解题模板 2.1.1 最长递增子序列长度 2.1.2 寻找数组中第 k 大的元素 2.1.3 最长公共子序列长度 2.1.4 寻找数组中第 k 小的元素 2

    2024年02月04日
    浏览(38)
  • 数据结构与算法-数组(附阿里面试题)

            给你一个文件里面包含全国人民(14亿)的年龄数据(0~180),现在要你统计每一个年龄   有多少人?          给定机器为 单台+2CPU+2G内存。不得使用现成的容器,比如map等。 (这一句可以忽略)         在以上情况下你该如何以最高效的方法来解决这个

    2024年02月13日
    浏览(28)
  • 【数据结构和算法】使用数组的结构实现链表(单向或双向)

    上文我们通过结构体的结构实现了队列 、以及循环队列的实现,我们或许在其他老师的教学中,只学到了用结构体的形式来实现链表、队列、栈等数据结构,本文我想告诉你的是,我们 可以使用数组的结构实现链表、单调栈、单调队列 目录 前言 一、用数组结构的好处 1.数

    2024年01月20日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包