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

树状数组

时间:2024-02-17 09:02:40浏览次数:26  
标签:树状 int lowbit 数组 区间 include

树状数组

背景

  • 由于 \(OIer\) 们对于数据更高效的储存、修改和查询的需要,一种数据结构树状数组营运而生。

介绍

  • 树状数组是一个查询和修改时间复杂度都为 \(O(log(n))\) 的数据结构,主要用于:数组的单点修改和区间查询

在使用前缀和求区间和的算法中:

如果可以做到在 \(O(1)\) 的时间复杂度内查询任意的区间和,但是如果要修改其中一个点的值,那么需要修改一遍前缀和数组,修改的时间复杂度是 \(O(n)\) 。

这是极不平衡的。

树状数组便克服了这个弊端。

  • 树状数组将整个数组分段处理,相对平衡了时间复杂度。

树状数组的实现

拆分原理

  • 正如所有的整数都可以表示成 \(2\) 的幂和,我们也可以把一串序列表示成一系列子序列的和。采用这个想法,我们可将一个前缀和划分成多个子序列的和,而划分的方法与数的 \(2\) 的幂和具有极其相似的方式
  • 比如:整数 \(21\) 的对应的 \(2\) 进制是 \(10101\) ,对应计算\(=2^4+2^2+2^1\) ,因此一个长度为 \(21\) 的数组,可以拆分为三段:

pFGgx58.png

  • 子序列的个数是其二进制表示中 \(1\) 的个数。

  • 据此,一个长度为 \(n\) 的区间 \([1,n]\) ,可以采用上述思想划分为 \(log(n)\) 个区间。

  • 从右向左看,每个区间的大小其实是 \(n\) 对应二进制的最后一个 \(1\) 的 \(2\) 次幂。

求对应二进制的最后一个1的方法

  • 定义一个函数 \(lowbit(x)\) :代表 \(x\) 对应二进制从最后一位开始向后构成的值。

    e.g:

    \(lowbit(10)\) 返回 \(10\)

    \(lowbit(40)\) 返回 \(1000\)

  • 计算方法:

    假设 \(x=270\) 对应的二进制为: \(1011,1000\) 。

    先将 \(x\) 取反 \(=0100,0111\) ,再 \(+1=0100,1000\) ,此时发现这个值和原来的 \(x\) 对应的二进制,最后一个 \(1\) 向后的值一致,前面的值正好相反,只要拿这两个值做 \(\&\) (按位与)运算,结果就是 \(lowbit(x)\) 的值。

    因此:\(lowbit(x)=x\&(~x+1)=x\&-x\)

划分方法

  • 以 \(a[x]\) 结尾的区间,区间长度为 \(x\) 的最后一个 \(1\) 所对应的 \(2\) 的次幂。

pFGWtjH.png

  1. 以 \(a[x]\) 结尾的区间,区间长度为 \(x\) 的最后一个 \(1\) 对应的 \(2\) 的次幂。

  2. 设定 \(c[x]\) 表示: \(a\) 数组中,长度为 \(lowbit(x)\) 的区间和,所管理的区间为 \([x-lowbit(x)+1],x\) 。

  3. 该结构的特点:

    a) 除树根外,结点 \(x\) 的父结点 \(=x+lowbit(x)\)

    b) 长度为 \(n\) 的数组 \(a\) ,所构建的树的深度 \(=log(n)\)

    注意:树状数组的下标一般从 \(1\) 开始,因为如果从 \(0\) 开始, \(lowbit(0)=0\) ,容易出现死循环。

修改元素

pFGHeg0.png

//在数组a的第x个数上增加k的值
void add(int x,int k){
	for(int i=x;i<=n;i+=lowbit(i)){
        c[x]+=k;
    }
}

求前缀和

pFGHmvV.png

//查询数组下标[1,x]中的数的和
int qu(int x){
	int res=0;
    for(int i=x;i>0;i-=lowbit(i)){
		res+=c[i];
    }
    return res;
}

模板

#include<algorithm>
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<string>
#include<vector>
#include<math.h>
#include<stack>
#include<queue>
#include<set>
#include<map>
using namespace std;
const int N=1e5+10;
int n;		//数的个数 
int c[N];	//前缀和 
int lowbit(int x){
	return x&-x;
}
void add(int x,int y){	//在x的位置加上y 
	for(int i=x;i<=n;i=i+lowbit(i)){
		c[i]+=y;
	}
	return;
}
int qu(int x){	//查询[1,x]的数的和 
	int res=0;
	for(int i=x;i>0;i=i-lowbit(i)){
		res+=c[i];
	}
	return res;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		add(i,x);
	}
	int m;
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		printf("%d\n",qu(r)-qu(l-1));
	}
	return 0;
}

标签:树状,int,lowbit,数组,区间,include
From: https://www.cnblogs.com/liuqichen121/p/18017703

相关文章

  • 树状数组-三色二叉树 题解
    题目在这里————————————————————————————————三色二叉树首先题面写的很清楚了是一道树状数组题因为这题的输入方式很特别按二叉树序列所以在输入上要特殊处理如下voidread(intx){//读入+存图以左右子树为形式如l[x]=y即y为x左子树......
  • 查找数组中最大元素,数组的打印,反转
    需求查找数组中最大元素,数组的打印,反转;学习点方法retrun的数在主方法中要定义元素接收,如反转数组返回一个数组,main方法中要定义一个新的数组用来接收返回的数组;数组循环可以使用增强for循环反转数组的for循环可以同时定义i和j,同时一个递增一个递减代码实现package......
  • 数组成鸡
    数组成鸡题目描述小鸡有一个由整数组成的数组,小鸡可以对这个数组进行任意次(可以不进行)全数组每个数加一或全数组每个数减一的操作。现在,小鸡想让你回答$Q$次询问,每次询问给出一个整数$M$,你需要回答任意次(可以不操作)操作后是否可以使得给定数组的乘积等于给出的整数$M$。输......
  • 数组元素关系映射——cf_925_D. Divisible Pairs
    目录问题概述思路分析参考代码做题反思问题概述原题参考:D.DivisiblePairs给出整数n、x、y和长度为n的数组,要求求出数组中满足以下关系的数对x|ai+ajy|ai-aji<j思路分析刚开始看到这个题的时候觉得没思路,坐牢卡半天发现感觉是对的(裂开)。题解给的是map的做法,看完之......
  • 后缀数组
    后缀数组后缀数组利用倍增思想求出sarank数组然后利用这两个数组求height最后利用height解决一些问题所以啥是sarankheight啊?算法流程定义1.后缀数组sa将按字典序排序后的后缀的开头位置顺次放入了sa中换个说法就是sa[i]表示字典序排名为i的后缀的开头的下标2.......
  • 912.排序数组--插入排序
    1.题目介绍给你一个整数数组nums,请你将该数组升序排列。示例1:输入:nums=[5,2,3,1]输出:[1,2,3,5]示例2:输入:nums=[5,1,1,2,0,0]输出:[0,0,1,1,2,5]2.题解2.1插入排序思路主要思路就是创建一个有序区域和无序区域,不断从无序区域取一张出来顺序插入有序区域即可代......
  • 912.排序数组--冒泡排序
    1.题目介绍给你一个整数数组nums,请你将该数组升序排列。示例1:输入:nums=[5,2,3,1]输出:[1,2,3,5]示例2:输入:nums=[5,1,1,2,0,0]输出:[0,0,1,1,2,5]2.题解2.1冒泡排序思路跟选择排序,固定一个i,后续者不断打擂台挑战不同,冒泡排序永远是两个邻接值比较,较大值不断向后冒......
  • 912.排序数组--选择排序
    1.题目介绍给你一个整数数组nums,请你将该数组升序排列。示例1:输入:nums=[5,2,3,1]输出:[1,2,3,5]示例2:输入:nums=[5,1,1,2,0,0]输出:[0,0,1,1,2,5]2.题解2.1插入排序思路打擂台,每次确定第一名,第二名,第三名,依次往后代码#include<bits/stdc++.h>usingnamespace......
  • leetcode——数组算法——前缀和构建和应用
    leetcode——数组算法——前缀和构建和应用前缀和技巧适用于快速、频繁地计算一个索引区间内的元素之和303.区域和检索-数组不可变比如leetcode303.区域和(检索-数组不可变)题目介绍:给定一个整数数组nums,处理以下类型的多个查询:计算索引left和right(包含left......
  • 树状数组模拟_ABC340_E - Mancala 2
    目录问题简述思路分析参考代码做题反思问题简述原题参考:E-Mancala2初始给出长度为n、m的数组a、b,要求给出m次操作后的数组a,每一次的操作流程如下:设定变量c=0;取出a[b[i]]中的数字保证手上有一个球的情况下进行以下操作:c++向a[(b[i]+c)%n]中放1可以看原题,原题有......