数据结构-树状数组专题(1)

一、前言

树状数组可以解决部分区间修改和区间查询的问题,相比于线段树,代码更加简单易懂

二、我的模板

搬运jiangly鸽鸽的模板,特别注意这个模板中所有涉及区间的都是左闭右开区间,且vector的有效下标都从0开始

template <typename T>
struct Fenwick {
    int n;
    std::vector<T> a;

    Fenwick(int n_ = 0) {
        init(n_);
    }

    void init(int n_) {
        n = n_;
        a.assign(n, T{});
    }

    void add(int x, const T &v) {
        for (int i = x + 1; i <= n; i += i & -i) {
            a[i - 1] = a[i - 1] + v;
        }
    }

    T sum(int x) {
        T ans{};
        for (int i = x; i > 0; i -= i & -i) {
            ans = ans + a[i - 1];
        }
        return ans;
    }

    T rangeSum(int l, int r) {
        return sum(r) - sum(l);
    }

    int select(const T &k) {
        int x = 0;
        T cur{};
        for (int i = 1 << std::__lg(n); i; i /= 2) {
            if (x + i <= n && cur + a[x + i - 1] <= k) {
                x += i;
                cur = cur + a[x - 1];
            }
        }
        return x;
    }
};

三、树状数组简介

在单点修改和区间查询(单点查询)中,暴力的算法时间复杂度会非常的高,如果想使用前缀和减少时间复杂度,但是我们发现由于频繁地单点修改,导致无法高效地维护前缀和数组,因此衍生出了我们的这个树状数组的高级数据结构。如下图:

9b76f19ee2314eb191f76afe574c1ce7.png

观察图,易发现:
C1=A1

C2=A1+A2

C3=A3

C4=A1+A2+A3+A4

C5=A5

C6=A5+A6

C7=A7

C8=A1+A2+A3+A4+A5+A6+A7+A8

于是我们引入lowbit的概念,就是一个数的二进制表示的最低位1表示的数值,例如6的二进制为110,因此10转换成十进制的值即为2,所以6的lowbit是2

关于计算lowbit,有很多方法,可以通过循环找按位右移去找,也可以用x-(x&(x-1))来计算lowbit,还可以用x&(-x)来计算lowbit(最常用也最简洁),至于原理则跟二进制有关,感兴趣的可以自行去了解

树状数组的性质

单点更新操作

如果要更新某个点,那么从这个点开始,下标一直加上当前的lowbit,直到大于最大长度n结束,都需要同时更新它们的值

查询前缀和

如果要查询前k个数的前缀和,那么前缀和的值就等于从k开始一直减去当前lowbit直到k等于0(k取不到0)结束的所有树状数组的值之和

实现区间查询

只需要实现两个前缀和的查询然后相减即可

假如要查询[l,r]这个区间,那么要求出pre[r]的值和pre[l-1]的值,pre表示前缀和

其中单点查询是区间查询的子情况,只不过此时的l等于r而已

后续的专题(2)还有区间修改+单点查询和区间修改+区间查询的高级应用(结合差分)

具体树状数组的细节可以自行翻阅资料,这里就不再展开讲

四、专题训练

4.1 星码StarryCoding P40 【模板】树状数组(单点修改)

6fc9b892827f42c8a32fb3d477a6db05.png

思路

树状数组的初始化操作实际上也是一个单点修改的操作,这是个模板题,没什么好说的,直接看代码吧

我的代码

注意可能爆int,因此要开long long,还有要注意下标!!!

//Code Here.
#include <bits/stdc++.h>
using i64 = long long;
constexpr int N = 2e5+5;

template <typename T>
struct Fenwick {
   int n;
   std::vector<T> a;

   Fenwick(int n_ = 0) {
       init(n_);
   }

   void init(int n_) {
       n = n_;
       a.assign(n, T{});
   }

   void add(int x, const T &v) {
       for (int i = x + 1; i <= n; i += i & -i) {
           a[i - 1] = a[i - 1] + v;
       }
   }

   T sum(int x) {
       T ans{};
       for (int i = x; i > 0; i -= i & -i) {
           ans = ans + a[i - 1];
       }
       return ans;
   }

   T rangeSum(int l, int r) {
       return sum(r) - sum(l);
   }

   int select(const T &k) {
       int x = 0;
       T cur{};
       for (int i = 1 << std::__lg(n); i; i /= 2) {
           if (x + i <= n && cur + a[x + i - 1] <= k) {
               x += i;
               cur = cur + a[x - 1];
           }
       }
       return x;
   }
};

int main() {
   std::cin.tie(nullptr)->sync_with_stdio(false);

   int n,q;
   std::cin >> n >> q;
   std::vector<i64> a(N,0);
   for(int i = 1;i <= n;++i) {
       std::cin >> a[i];
   }
   Fenwick<i64> fenwick(N);
   for(int i = 1;i <= n;++i) {
       fenwick.add(i-1,a[i]);
   }
   while(q--) {
       int op,l,r,k,v;
       std::cin >> op;
       if(op==1) {
           std::cin >> k >> v;
           fenwick.add(k-1,v);
       }else {
           std::cin >> l >> r;
           std::cout << fenwick.sum(r)-fenwick.sum(l-1) << '\n';
       }
   }
   
   return 0;
}

4.2 星码StarryCoding P31 求逆序对个数

ae90638a05374c44b0f16acb6b166a52.png

思路

提示很明显了,用归并排序或者树状数组都能写,用树状数组写的时候要注意,由于1<=ai<=1e9,因此不能直接开树状数组,会爆栈,所以需要先对a数组做离散化,再开一个树状数组,数组大小为离散化后的数组大小,数组存放的内容是当前下标对应的离散化数组中的值出现的次数(听着有一点绕,可以看下面的代码辅助理解),然后i从1到n遍历,找当前比a[i]大的值(当前比a[i]大的值全都存储在当前树状数组下标的后面,因此可以用区间查询直接算)有多少个,答案就加上多少,然后修改当前的值加一,最后累加得到的答案输出即可

我的代码1

树状数组实现

#include <bits/stdc++.h>
using i64 = long long;
constexpr int N = 2e5+5;

template <typename T>
struct Fenwick {
    int n;
    std::vector<T> a;

    Fenwick(int n_ = 0) {
        init(n_);
    }

    void init(int n_) {
        n = n_;
        a.assign(n, T{});
    }

    void add(int x, const T &v) {
        for (int i = x + 1; i <= n; i += i & -i) {
            a[i - 1] = a[i - 1] + v;
        }
    }

    T sum(int x) {
        T ans{};
        for (int i = x; i > 0; i -= i & -i) {
            ans = ans + a[i - 1];
        }
        return ans;
    }

    T rangeSum(int l, int r) {
        return sum(r) - sum(l);
    }

    int select(const T &k) {
        int x = 0;
        T cur{};
        for (int i = 1 << std::__lg(n); i; i /= 2) {
            if (x + i <= n && cur + a[x + i - 1] <= k) {
                x += i;
                cur = cur + a[x - 1];
            }
        }
        return x;
    }
};

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n,q;
    std::cin >> n;
    std::vector<int> a(N,0),v;//v是离散化数组
    for(int i = 1;i<=n;++i) {
        std::cin >> a[i];
        v.emplace_back(a[i]);
    }

    v.emplace_back(0);//放一个0是为了排序后的有效下标从1开始
    //排序去重
    std::sort(v.begin(),v.end());
    v.erase(std::unique(v.begin(),v.end()),v.end());

    Fenwick<int> fenwick(N);
    i64 ans = 0;
    for(int i = 1;i<=n;++i) {
        int idx = std::lower_bound(v.begin(),v.end(),a[i])-v.begin();
        ans+=fenwick.sum(v.size()-1)-fenwick.sum(idx);
        fenwick.add(idx-1,1);
    }
    std::cout << ans << '\n';

    return 0;
}

我的代码2

归并排序实现

#include <bits/stdc++.h>
using i64 = long long;
constexpr int N = 2e5+5;

i64 ans = 0;
std::vector<int> v(N,0);
std::vector<int> tmp(N,0);

using MERGE_SORT = std::function<void(std::vector<int> &,int,int)>;
MERGE_SORT mergeSort = [](std::vector<int> &v,int l,int r)->void {
    if(l>=r) return;
    int mid = l+r>>1;
    mergeSort(v,l,mid);
    mergeSort(v,mid+1,r);
    int k = 0,i = l,j = mid+1;
    while(i<=mid&&j<=r) {
        if(v[i]<=v[j]) tmp[k++] = v[i++];
        else tmp[k++] = v[j++],ans+=mid-i+1;
    }
    while(i<=mid) tmp[k++] = v[i++];
    while(j<=r) tmp[k++] = v[j++];
    for(int i = l,j = 0;i<=r;i++,j++) v[i] = tmp[j];
};

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n;
    std::cin >> n;
    
    for(int i = 1;i<=n;i++) {
        std::cin >> v[i];
    }
    
    mergeSort(v,1,n);
    std::cout << ans << '\n';
    
    return 0;
}

五、后记

难点在后面与差分结合的区间修改,不过很多人不去学这块,毕竟线段树可以很简单地实现(思维上简单,代码量确实不简单,写着写着就Segment Fault了,不然怎么叫Segment Tree呢[/滑稽])

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux网络——套接字编程

1. 网络通信基本脉络 基本脉络图如上&#xff0c;其中数据在不同层的叫法不一样&#xff0c;比如在传输层时称为数据段&#xff0c;而在网络层时称为数据报。我们可以在 Linux 中使用 ifconfig 查看网络的配置&#xff0c;如图 其中&#xff0c;inet 表示的是 IPv4&#xff0c;…

‘视’不可挡:OAK相机助力无人机智控飞行!

南京邮电大学通达学院的刘同学用我们的oak-d-lite实现精确打击无人机的避障和目标识别定位功能&#xff0c;取得了比赛冠军。我们盼望着更多的朋友们能够加入到我们OAK的队伍中来&#xff0c;参与到各式各样的比赛中去。我们相信&#xff0c;有了我们相机的助力&#xff0c;大家…

复旦微电子FM33LC046U在keil工程中无法使用j-link下载问题解决

在Keil环境下使用JLINK工具下载程序&#xff0c;发现J-link V7.89a无法识别FM33LC046U&#xff0c;提示如下&#xff1a; 选择Cortex-M0 设置为SW模式&#xff0c;即可识别到芯片 经过如上步骤&#xff0c;就可以使用Jlink下载和仿真程序

java中设计模式的使用(持续更新中)

概述 设计模式的目的&#xff1a;编写软件过程中&#xff0c;程序员面临着来自耦合性&#xff0c;内聚性以及可维护性&#xff0c;可扩展性&#xff0c;重用性&#xff0c;灵活性等多方面的挑战&#xff0c;设计模式是为了让程序&#xff08;软件&#xff09;&#xff0c;具有…

【计算机网络实验】之静态路由配置

【计算机网络实验】之静态路由配置 实验题目实验目的实验任务实验设备实验环境实验步骤路由器配置设置静态路由测试路由器之间的连通性配置主机PC的IP测试 实验题目 静态路由协议的配置 实验目的 熟悉路由器工作原理和机制&#xff1b;巩固静态路由理论&#xff1b;设计简单…

【PS】矢量绘图技巧

1、先使用钢笔工具结合ctrl和alt建将苹果大致扣出来。 任意选择一个颜色进行填充 新建一个图层&#xff0c;使用渐变工具为图层添加渐变颜色 选择剪切蒙版&#xff0c;将图层颜色填入苹果&#xff0c;得最终结果。 内容二、麦当劳 与内容一类似的&#xff0c;使用钢笔工具将M形…

【HCIP]——OSPF综合实验

题目 实验需求 根据上图可得&#xff0c;实验需求为&#xff1a; 1.R5作为ISP&#xff1a;其上只能配置IP地址&#xff1b;R4作为企业边界路由器&#xff0c;出口公网地址需要通过PPP协议获取&#xff0c;并进行CHAP认证。&#xff08;PS&#xff1a;因PPP协议尚未学习&#…

django启动项目报错解决办法

在启动此项目报错&#xff1a; 类似于&#xff1a; django.core.exceptions.ImproperlyConfigured: Requested setting EMOJI_IMG_TAG, but settings are not c启动方式选择django方式启动&#xff0c;以普通python方式启动会报错 2. 这句话提供了对遇到的错误的一个重要线索…

【GeekBand】C++设计模式笔记12_Singleton_单件模式

1. “对象性能” 模式 面向对象很好地解决了 “抽象” 的问题&#xff0c; 但是必不可免地要付出一定的代价。对于通常情况来讲&#xff0c;面向对象的成本大都可以忽略不计。但是某些情况&#xff0c;面向对象所带来的成本必须谨慎处理。典型模式 SingletonFlyweight 2. Si…

计算机网络 (1)互联网的组成

一、互联网的边缘部分 互联网的边缘部分由所有连接在互联网上的主机组成&#xff0c;这些主机又称为端系统&#xff08;end system&#xff09;。端系统可以是各种类型的计算机设备&#xff0c;如个人电脑、智能手机、网络摄像头等&#xff0c;也可以是大型计算机或服务器。端系…

电商行业客户服务的智能化:构建高效客户服务知识库

在电商行业&#xff0c;客户服务是提升用户体验和品牌忠诚度的关键。随着数字化转型的深入&#xff0c;构建一个高效的客户服务知识库变得尤为重要。本文将探讨电商行业如何构建客户服务知识库&#xff0c;并分析其在提升服务质量中的作用。 客户服务知识库的重要性 客户服务…

CentOS 9 无法启动急救方法

方法一&#xff1a;通过单用户安全模式启动 开机按上下方向键&#xff0c;选择需要启动的内核&#xff0c;按e键进入配置模式 修改配置 ro 改 rw 删除 rhgb quiet 末尾增加 init/bin/bash 按 Ctrlx 启动单用户模式 如果想重新启动&#xff0c;重启电脑 执行 exec /sbin/in…

数字后端零基础入门系列 | Innovus零基础LAB学习Day11(Function ECO流程)

###LAB 20 Engineering Change Orders (ECO) 这个章节的学习目标是学习数字IC后端实现innovus中的一种做function eco的flow。对于初学者&#xff0c;如果前面的lab还没掌握好的&#xff0c;可以直接跳过这节内容。有时间的同学&#xff0c;可以熟悉掌握下这个flow。 数字后端…

SAM-Med2D 训练完成后boxes_prompt没有生成mask的问题

之前对着这这篇文章去微调SAM_Med2D(windows环境),发现boxes_prompt空空如也。查找了好长时间问题SAM-Med2D 大模型学习笔记&#xff08;续&#xff09;&#xff1a;训练自己数据集_sam训练自己数据集-CSDN博客 今天在看label2image_test.json文件的时候发现了一些端倪: 官方…

java ssm 同仁堂药品管理系统 在线药品信息管理 医药管理源码jsp

一、项目简介 本项目是一套基于SSM的同仁堂药品管理系统&#xff0c;主要针对计算机相关专业的和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本、软件工具等。 项目都经过严格调试&#xff0c;确保可以运行&#xff01; 二、技术实现 ​后端技术&…

使用阿里云快速搭建 DataLight 平台

使用阿里云快速搭建 DataLight 平台 本篇文章由用户 “闫哥大数据” 分享&#xff0c;B 站账号&#xff1a;https://space.bilibili.com/357944741?spm_id_from333.999.0.0 注意&#xff1a;因每个人操作顺序可能略有区别&#xff0c;整个部署流程如果出现出入&#xff0c;以…

如何解决VS Code的Live Share会话中Guest无法看到共享的文件夹?

在 VS Code 的 Live Share 会话中&#xff0c;如果 Guest 无法看到共享的文件夹&#xff0c;如图所示&#xff1a; 可能是因为权限设置、浏览器限制或 Live Share 的配置问题。以下是逐步排查和解决问题的方法&#xff1a; 1. 确保正确共享了文件夹 在主机&#xff08;Host&a…

.NET 9 运行时中的新增功能

本文介绍了适用于 .NET 9 的 .NET 运行时中的新功能和性能改进。 文章目录 一、支持修剪的功能开关的属性模型二、UnsafeAccessorAttribute 支持泛型参数三、垃圾回收四、控制流实施技术.NET 安装搜索行为性能改进循环优化感应变量加宽Arm64 上的索引后寻址强度降低循环计数器可…

深入解析TK技术下视频音频不同步的成因与解决方案

随着互联网和数字视频技术的飞速发展&#xff0c;音视频同步问题逐渐成为网络视频播放、直播、编辑等过程中不可忽视的技术难题。尤其是在采用TK&#xff08;Transmission Keying&#xff09;技术进行视频传输时&#xff0c;由于其特殊的时序同步要求&#xff0c;音视频不同步现…

MongoDB:数据迁移

业余人员学习 第一种:通过MongoDB命令 参考链接: MongoDB的备份(mongodump)与恢复(mongorestore)_MongoDB_脚本之家 MongoDB数据库管理:全面掌握mongodump和mongorestore的备份与恢复技巧_8055096的技术博客_51CTO博客 1.1、首先进入操作命令行,都不需要进入MongoDB […