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

树状数组

时间:2023-09-20 21:38:13浏览次数:39  
标签:ft 树状 int lowbit 查询 数组

树状数组 ( \(\text{fenwick tree}\) ) 是主要用于前缀信息维护的一维数组 ——《信息学奥林匹克辞典》

基础树状数组

维护信息

维护一个数列的元素的操作

可进行的操作

  1. 单点修改,即修改数列中其中一个元素的值

  2. 区间查询,即查询数列中连续一段区间的值进行某种运算

存储方法

树状数组的本体是一个数组

它巧妙地运用了二进制的思想,将树状数组本体中的每一个元素所管辖的范围由其下标决定。

具体地,若设其本体为 \(\text{C++}\) 中的数组 ft,则 ft[i] 所管辖的范围为 \([\) i - lowbit(i) + 1 , i \(]\)

若还不理解的话,可看下面的图:

树状数组

操作方法

单点修改

易发现,修改一个点,只需修改它本身与其祖先即可。

那我们怎么快速得到一个数的父亲节点呢?

既然自己管辖了 lowbit(i) 个,那它的兄弟肯定也管辖了 lowbit(i) 个节点,而我们发现,因为此时我们已经是这一列的最上层了,所以,它的兄弟的下标实际上就是它们父亲的下标!

所以:fa = i + lowbit(i)

又因为它最多有 \(\log n\) 个祖先,所以它时间复杂度为 \(\Theta(\log n)\)

区间查询

我们先思考一个更简单的问题:怎么查询 \(1\) 到 \(x\) 的运算起来的值呢?

由于我们要尽可能地应用我们已知的值,则试图找到一个满足 \(\log_2k\in\mathbb Z 且 k \le n\),此时,我们可以把 ft[k] 的贡献算上,然后问题就转化成了查询 \(k+1\) 到 \(x\) 的运算起来的值,注意到因为 \(\log_2k \in \mathbb Z\),所以可以用 \(1\) 到 \(x\) 的方法来算。

回到原问题,我们就可以用前缀和的思想来解决它了。

容易发现,每计算一次,区间大小都至少会减半,故其时间复杂度为 \(\Theta(\log n)\)。

代码

以加法为例。

int n;
int ft[MAXN];
int lowbit(int x) {
    return x & (-x);
}
int sum(int x, int y) {
    int s = 0;
    for (int i = y; i >= 1; i -= lowbit(i)) {
        s += ft[i];
    }
    for (int i = x - 1; i >= 1; i -= lowbit(i)) {
        s -= ft[i];
    }
    return s;
}
void add(int x, int v) {
    for (int i = x; i <= n; i += lowbit(i)) {
        ft[i] += v;
    }
}

差分+树状数组

可进行的操作

  1. 区间修改

  2. 单点查询

操作方法

容易想到使用差分来把它转化成基础树状数组,即用它维护差分数组,然后单点查询变成了区间查询,区间修改变成了单点修改。

代码

int n;
int ft[MAXN];
int lowbit(int x) {
    return x & (-x);
}
int sum(int x) {
    int s = 0;
    for (int i = x; i >= 1; i -= lowbit(i)) {
        s += ft[i];
    }
    return s;
}
void add(int x, int y, int v) {
    for (int i = x; i <= n + 1; i += lowbit(i)) {
        ft[i] += k;
    }
    for (int i = y+1; i <= n + 1; i += lowbit(i)) {
        ft[i] -= k;
    }
}

二维树状数组

维护信息

它所维护的是矩阵内的元素的操作。

可进行的操作

  1. 单点修改

  2. 子矩阵查询

存储方法

一个二维数组,所维护的东西简单粗暴地把树状数组里套了个树状数组。

操作方法

实际上也很简单粗暴,就是把每个元素当作一个树状数组再进去操作。

注意,它的时间复杂度为 \(\Theta(\log^2 n)\)。

代码

int n, ft[MAXN][MAXN];
int lowbit(int x) {
    return x & (-x);
}
void add(int x, int y, int z) {
    for (int i = x; i <= n; i += lowbit(i))
        for (int j = y; j <= n; j += lowbit(j))
            ft[i][j] += z;
}
int query(int x, int y) {
    int res = 0;
    for (int i = x; i >= 1; i -= lowbit(i))
        for (int j = y; j >= 1; j -= lowbit(j))
            res += ft[i][j];
    return res;
}

  1. 文中出现的 lowbit 指的是该数在二进制状态下的最后一个 \(1\) 所表示的数,在补码下可用 x & (-x) 来计算

标签:ft,树状,int,lowbit,查询,数组
From: https://www.cnblogs.com/LightningCreeper/p/Fenwick-Tree.html

相关文章

  • JavaScript数组filter方法
    1.数组filter方法作用筛选数组,将满足条件的元素放入新数组中2.语法:array.filter(function(item,index,arr){})第一个参数:item,必须,当前元素的值第二个参数:index,可选,当前元素在数组中的索引值第三个参数:arr,当前元素所处的数组对象3.filter方法特点(1)函......
  • ES6中数组新增了的扩展
    扩展运算符的应用ES6通过扩展元素符...,比如 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列console.log(...[1,2,3])//123console.log(1,...[2,3,4],5)//12345[...document.querySelectorAll('div')]//[<div>,<div>,<div>]主要用于函数调用的时候......
  • javascript处理数组
     letdata=[{"subject_id":948,"xmdw":"长春市实验中学","sbnd":2023,"xmmc":"长春市实验中学食堂厨具设备更换项目"},{"subject_id":949,"x......
  • 算法学习 |Day 1 数组基础 704. 二分查找,27. 移除元素
    704.二分查找思路:二分查找的前置条件是数组有序且无重复元素,每次通过改变边界值来缩小查找范围。自己写的:可以看到对边界的判断存在问题,基本思路是左闭右闭,但是while循环的判断是按照左闭右开来写的。对于数组中仅包含一个元素且该元素是目标函数的情况会出错。重新调试后......
  • 树结构转数组/数组转树结构
    树结构转数组constlistTree=[{id:1,name:'部门1',pid:0,children:[{id:2,name:'部门1-1',pid:1,children:[......
  • Java学习之路--array--数组
    packagecom.chao.array;/*数组定义:1.数组市相同类型数据的有序集合2.数组描述的是相同类型的若干个数据,按照一定的先后顺序排列组合而成3.其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们数组声明创建首先必须声明数组变量,才能在程序中使用数组,声明数组变......
  • 每日一题:如何判断是否是数组,一个既简单又复杂的问题。(不要再用Object.prototype.toStr
    1、不要使用Object.prototype.toString.call()正常情况下:constarr=[1,2,3,4,5]constobj={}console.log(Object.prototype.toString.call(arr))//[Object,Array]console.log(Object.prototype.toString.call(obj))//[Object,Object]过去我们能够通过判断Object.proto......
  • 七天学会C语言-第四天(数组)
    1.定义一维数组在C语言中,一维数组是具有相同数据类型的元素的有序集合。定义一维数组的基本语法如下:data_typearray_name[array_size];其中:data_type 是数组元素的数据类型,可以是整数、浮点数、字符等。array_name 是数组的名称,你可以自定义。array_size 是数组的大小,指定了数......
  • 《剑指Offer》-21-调整数组顺序使奇数位于偶数前面
    第一想法是双指针,一个指针用于遍历,一个指针用于标记奇数和偶数的分界,而调整位置则通过交换来实现思路来自于快排代码,分隔指针+交换,也算是双指针? vector<int>exchange(vector<int>&nums){ //一个遍历指针,一个分隔指针,odd指向第一个偶数 intodd=0; for(inti=0;i......
  • arcgis for js4.x自定义Graphic数组创建FeatureLayer添加标注
    varpoint=[{ "geometry":{ "x":116.820688, "y":33.974053, "spatialReference":{ "wkid":4326 } }, "......