首页 > 其他分享 >【复健】树状数组2

【复健】树状数组2

时间:2024-01-15 17:25:37浏览次数:30  
标签:复健 树状 int 补码 pos 数组 区间

树状数组复健2

展开目录

目录

注:因为习惯和省事问题,下文的 \(lowbit\) 代表 \(lowerbit\),但后者也会时而出现。

为什么重写

image

↑您不觉得我这玩意写得逻辑不通吗而且抽象

关于树状数组

是什么

并不是一种树形结构。

image

↑按照我的理解这玩意差不多长成这个样,类似砖头

通过一些分散的小区间来储存数据,每个数据可能会同时出现在多个区间内。包含同一个数据相邻两个区间之间间隔 \(lowerbit(x)\),即截留下这个数的最末位 \(1\)及其之后的 \(0\) 所形成的数。

为什么

快(这不废话吗

对于一个普通数组,单点修改的复杂度是 \(O(1)\),区间查询的复杂度是 \(O(n)\);对于一个前缀和数组,区间查询的复杂度是 \(O(1)\),但单点修改的复杂度是 \(O(n)\).

其中 \(n\) 为区间长度。

树状数组单点修改和区间查询的复杂度都是 \(O(\log n)\).

另外一点是,如果实在没时间打线段树,树状数组的码量会小一点。

关于 lowbit 运算

\(e.g.\)

\(x = (11001)_2\) 是一个二进制奇数,我们知道 \(-x = (10011001)_2\), 即把符号位变成 \(1\)。

\(x\) 的补码是原码,\(-x\) 的补码就是它的反码 \(+1\), 而:

image

所以可得 \(x\) 的补码为 \((11010)_2\), \(-x\) 的补码为 \((11100111)_2\), \(x \& (-x) = (1)_2\), 就取到了 \(x\) 的最后一位 \(1\).

因为正数的补码和原码相同,负数的补码和对应的正数相比,除了最后一位 \(1\) 及其以后的 \(0\), 其它的全部取反,按位与结果为 \(0\). 这样就获取到末位 \(1\) 了。

偶数比如 \(10000\) 和 \(10010000\), 显然后者在取反后会变为 \(11101111\), 实际上是与 \(10000\) 完全相反(包括符号位)的,所以 \(+1\) 后我们就会看到 \(-x\) 的补码为 \(11110000\),从而得到 \(10000\) 最末位的 \(1\).


代码实现

因为是砖头(?)式排列 法,可以参考在砖墙上涂鸦(?

修改一种砖头的纹路需要把所有包含这种纹路的砖头都修改,查询类比前缀和。

其实不看成砖头理解起来也没有任何麻烦(

注:默认原始数组为 \(a\),树状数组为 \(bit\),数组大小为 \(n\).

单点修改

因为区间间隔是 \(lowbit(x)\),所以循环的步长就是这么多。

void update1(int i, int x) {
	for(int pos = i; pos <= n; pos += lowbit(pos)) bit[i] += x;
}

区间修改

只需要把单点修改丢进 \(for\) 循环即可:

void update2(int l, int r, int x) {
	for(int i = l; i <= r; ++i) update1(i, x);
}

求前 n 项和

第一个区间不一定包含第 \(n\) 项,对其执行 \(lowbit\) 操作所得到的区间也不一定包含。

但是反过来,先查询第 \(n\) 个区间是可以的。第 \(n\) 个区间一定包含第 \(n\) 项。

为什么?

再看看这行代码:for(int pos = i; pos <= n; pos += lowbit(pos)),在初始化时我们必然使用 update1 操作来初始化每个编号为 \(i\) 的点,而这个操作是从第 \(i\) 个区间开始的。

int query1(int i) {
	int ans = 0;
	for(int pos = i; pos; pos -= lowbit(x)) ans += bit[pos];
	return ans;
}

区间查询

类比前缀和。

int query2(int l, int r) {
	return query1(r) - query1(l - 1); 
}

如果想要单点查询, 直接 query(i, i) 即可。

例题们

咕咕咕

标签:复健,树状,int,补码,pos,数组,区间
From: https://www.cnblogs.com/Kiichi/p/17956597/BITfujian2

相关文章

  • Java小细节之数组什么情况下相等,什么情况下不相等
    int[]a={1,2,3};int[]b=a;System.out.println(a==b);此时输出trueint[]a={1,2,3};int[]b={1,2,3};System.out.println(a==b);此时输出为false这是因为数组的机制,int[]b=a,相当于让b和a同时管理这个数组,a和b都是代表同一个数组,所以a==b是正确的,此时对数......
  • 吴师兄学算法day07 167. 两数之和 II - 输入有序数组
    题目:167. 两数之和II-输入有序数组易错点:下标为1开始我的代码:classSolution:deftwoSum(self,numbers:List[int],target:int)->List[int]:right=len(numbers)-1left=0whileleft<right:ans=numbers[left]......
  • Java 使用 数组实现 动态数组
    前述数组是各编程语言中最为基础的一个数据结构,在Java语言中,平时使用也很多,同时,JDK提供了动态数组的实现,ArrayList,这里我使用数组来实现一下动态数组,参考实现importjava.util.function.Consumer;/***使用数组实现动态数组ArrayList*/publicclassDynamicArray......
  • 数组 数组的内存 面向对象 this
    静态初始化全写:数据类型[]数组名=new数据类型{值};简写:数据类型[]数组名={值};动态初始化数据类型[]数组名=new数据类型[数组长度];数组不赋值时会默认初始化一个值整数:0小数:0.0字符:"/uoooo"(显示出来就是一个空格)布尔:FALSE引用数据类型:null数组的内存堆内存......
  • 经典数据结构题目-数组
    704.二分查找解决思路基于数组有序的特性,取其中一个值进行比较,即可淘汰该值左边或右边的元素,缩小搜索的区间使用两指针标记需要遍历的区间,取中间值进行比较,淘汰左边或右边元素,不断移动缩小遍历的区间,即可查到代码publicintsearch(int[]nums,inttarget){......
  • 后缀数组 SA 学习笔记
    后缀数组SA学习笔记后缀数组处理字符串后缀排名,公共子串类问题十分优秀,可以在部分情况下替代后缀自动机(SAM),本文主要讲解后缀数组的实现过程和部分例题。正文定义后缀:从\(i\)开始到字符串结束的一个特殊子串,本文用\(suf(i)\)表示从\(i\)开始的后缀。后缀数组SA:SA是......
  • java中数组和字符串
    数组数组的声明方式:类型[]变量;数组的创建方式:new类型[数组长度]数组的简单声明并且赋值//声明一个数组,它的长度是3String[]arrs=newString[3];arrs[0]="张三";arrs[1]="李四";//访问数组的值System.out.println(arrs[0]);输出的是张三//获取当前数组的长......
  • 超级简单的后缀数组(SA)笔记!!
    超级简单的后缀数组(SA)!!(未完)前言这里选择当一手标题党。由于刚学完这个字符串算法,本人字符串算法又比较薄弱,好不容易这一次在晚修看各种资料看得七七八八,决定趁脑子清醒的时候记录下来。免得自己不久后忘了后又要痛苦地再看各种资料。希望这篇博客能帮到你。前置知识:RMQ问题......
  • 用数组作为函数参数来实现冒号排序函数
    define_CRT_SECUNRE_NO_WARNINGS1include<stdio.h>voidbubble_sort(intarr[],intsz){inti=0;for(i=0;i<sz;i++)//冒泡的次数{intflag=1;//假设这一趟排序已经有序intj=0;for(j=0;j<sz-1-i;j++){if(arr[j]>arr[j+1]){inttmp......
  • P52 数组中出现次数超过一半的数字
    题目链接:方法一:若数组中有数字的出现次数超过数组长度的一半(绝对众数),则将该数组排序后中间位置的数一定就是该数。classSolution{public:intmoreThanHalfNum_Solution(vector<int>&nums){sort(nums.begin(),nums.end());intn=nums.size();......