树状数组笔记

树状数组是对一个数组改变某个元素和求和比较实用的数据结构。两中操作都是O(logn)。
传统数组(共n个元素)的元素修改和连续元素求和的复杂度分别为O(1)和O(n)。树状数组通过将线性结构转换成伪树状结构(线性结构只能逐个扫描元素,而树状结构可以实现跳跃式扫描),使得修改和求和复杂度均为O(lgn),大大提高了整体效率。

给定序列(数列)A,我们设一个数组C满足

C[i] = A[i–2^k+ 1] + … + A[i]

其中,k为i在二进制下末尾0的个数,i从1开始算!

则我们称C为树状数组。

下面的问题是,给定i,如何求2^k?

答案很简单:2k=i&(i(i-1)) ,也就是i&(-i) 为什么呢?? 请看下面:

整数运算 x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。
因为:x &(-x) 就是整数x与其相反数(负号取反)的按位与:1&1=1,0&1 =0, 0&0 =1。具体分析如下:
□ 当x为0时,x&(-x) 即 0 & 0,结果为0;
□ 当x不为0时,x和-x必有一个为正。不失一般性,设x为正。
●当x为奇数时,最后一个比特为1,取反加1没有进位,故x和-x除最后一位外前面的位正好相反,按位与结果为0。最后一位都为1,故结果为 1。
●当x为偶数,且为2的m次方(m>0)时,x的二进制表示中只有一位是1(从右往左的第m+1位),其右边有m位0,左边也都是0(个数由表示 x的字 节数决定),故x取反加1后,从右到左第有m个0,第m+1位及其左边全是1。这样,x& (-x) 得到的就是x。
●当x为偶数,却不为2的m次方的形式时,可以写作x= y * (2^k)。其中,y的最低位为1。实际上就是把x用一个奇数左移k位来表示。这时,x的 二进制 表示最右边有k个0,从右往左第k+1位为1。当对x取反时,最右边的k位0变成1,第k+1位变为0;再加1,最右边的k位就又变成了0,第 k+1位因为进 位的关系变成了1。左边的位因为没有进位,正好和x原来对应的位上的值相反。二者按位与,得到:第k+1位上为1,左边右边都为 0。结果为2^k,即 x中包含的2的最大次方的因子。
总结一下:x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。 比如x=32,其中2的最大次方因子 为 2^5,故x&(-x)结果为32;当x=28,其中2的最大次方因子为4,故x & (-x)结果为4。当x=24,其中2的最大次方因子为8,故 x&(-x)结果为 8。

下面进行解释:

以i=6为例(注意:a_x表示数字a是x进制表示形式):

(i)_10 = (0110)_2

(i-1)_10=(0101)_2

i xor (i-1) =(0011)_2

i and (i xor (i-1)) =(0010)_2

2^k = 2

C[6] = C[6-2+1]+…+A[6]=A[5]+A[6]

数组C的具体含义如下图所示:

当我们修改A[i]的值时,可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,这个操作的复杂度在最坏情况下就是树的高度即O(logn)。另外,对于求数列的前n项和,只需找到n以前的所有最大子树,把其根节点的C加起来即可。不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开成2的幂方和时的项数,因此,求和操作的复杂度也是O(logn)。

树状数组能快速求任意区间的和:A[i] + A[i+1] + … + A[j],设sum(k) = A[1]+A[2]+…+A[k],则A[i] + A[i+1] + … + A[j] = sum(j)-sum(i-1)。

排兵布阵

#include<iostream>
#include <bits/stdc++.h>
using namespace std;

int a[51000];
int b[51000];
int lowbit(int n)
{
    return n & (-n);
}
void Add(int x,int y,int n)
{
    //cout<<"jajfsj"<<endl;
    for(int i=x; i<=n; i+=lowbit(i))
        b[i]+=y;
}
void Sub(int x,int y,int n)
{
    for(int i=x; i<=n ; i+=lowbit(i))
        b[i]-=y;
}
int Query(int x,int y,int n)
{
    long long ans1=0;
    long long ans2=0;
    for(int i=y; i>=1; i-=lowbit(i))
        ans1+=b[i];
    for(int i=x-1;i>=1;i-=lowbit(i))
        ans2+=b[i];
    return ans1-ans2;
}

int  main()
{
    int t;
    cin>>t;
    for(int i=1; i<=t; i++)
    {
        int n;
        printf("Case %d:\n",i);
        cin>>n;
        memset(b,0,sizeof b);
        for(int k=1; k<=n; k++)
        {
            cin>>a[k];
            Add(k,a[k],n);
        }
        //solve(n);
        string s;
        for(cin>>s ; s!="End"; cin>>s)
        {
            if(s=="End")
                continue;
            if(s=="Add")
            {
                int x,y;
                cin>>x>>y;

                Add(x,y,n);
            }
            else if(s=="Sub")
            {
                int x,y;
                cin>>x>>y;

                Sub(x,y,n);
            }
            else
            {
                int x,y;
                cin>>x>>y;
                int ans=Query(x,y,n);
                cout<<ans<<endl;
            }
        }

    }
    return 0;
}
<think>好的,我现在要帮用户查找关于树状数组的学习资料或教程。用户已经提供了三个引用内容,我需要先仔细阅读这些引用,理解它们的关键点,然后结合自己的知识来整理回答。 首先看引用[1],里面提到树状数组的特点是编程简洁,使用lowbit技术,代码效率高,比线段树更高效。这里可能需要解释一下lowbit是什么,以及为什么代码效率更高。引用[2]则更详细地介绍了树状数组的定义,用途是处理范围查询和单点更新,比线段树快。而引用[3]说明了当修改数组元素时,前缀和需要重新计算,树状数组解决了这个问题。 用户的问题是需要学习树状数组的教程或详解,所以我的回答需要结构清晰,分步骤讲解,可能包括概念、原理、核心操作、代码示例以及应用场景。同时,根据用户提供的系统级指令,数学公式要用$...$,独立公式用$$...$$,并且生成相关问题。 接下来要确保回答符合中文,正确使用LaTeX格式。比如lowbit的公式应该是$lowbit(x) = x \& (-x)$,需要用行内公式格式。此外,可能需要给出树状数组的结构示意图,但由于文本限制,可能需要用文字描述结构。 然后需要组织内容:先介绍树状数组的基本概念,然后讲解lowbit的原理,接着说明核心操作(更新和查询),给出代码示例,再讨论应用场景,最后根据引用内容添加参考资料。同时,在回答末尾生成相关问题,帮助用户深入理解。 要注意引用标记,在引用内容后自然添加[^1]、[^2]等。例如在讲解lowbit时引用[1],在解释应用场景时引用[2]和[3]。 需要检查是否有遗漏的重要点,比如树状数组的优缺点,与其他数据结构(如线段树)的比较,这些可能对用户有帮助。同时,代码示例需要正确,可能用Python或C++,但用户提供的引用中有C++,所以可能更倾向用C++示例,但用户之前的示例是Python,所以可能需要兼顾或说明。 最后,生成的相关问题要围绕树状数组的深入理解、应用、比较等方面,比如与线段树的区别,时间复杂度,如何扩展到多维等。 现在需要把这些思考整理成一个连贯的回答,确保每个部分自然衔接,引用正确,格式符合要求。</think>### 树状数组详解 树状数组(Binary Indexed Tree, BIT)是一种高效处理**单点更新**和**前缀和查询**的数据结构,尤其适合动态维护数组的场景。其核心思想是通过二进制索引的树状结构,将时间复杂度优化到$O(\log n)$。 #### 1. 基本原理与结构 树状数组利用二进制中$lowbit$技术划分区间。定义$lowbit(x) = x \& (-x)$,例如$lowbit(6)=2$(6的二进制为$110$,取最低位的1)[^1]。 树状数组中每个节点$c[i]$管理原数组$arr$的区间为$[i-lowbit(i)+1, i]$,例如: - $c[4]$管理$arr[1-4]$(因为$lowbit(4)=4$) - $c[6]$管理$arr[5-6]$(因为$lowbit(6)=2$) #### 2. 核心操作 **(a) 单点更新** 当修改$arr[k]$时,需更新所有覆盖$k$的节点。例如更新$arr[3]$时,依次更新$c[3], c[4], c[8], \dots$ **代码示例(C++)**: ```cpp void update(int k, int val) { while (k <= n) { c[k] += val; k += lowbit(k); } } ``` **(b) 前缀和查询** 求$arr[1..k]$的和时,从$c[k]$开始向祖先跳跃累加。例如查询$k=6$时,累加$c[6], c[4]$ **代码示例(C++)**: ```cpp int query(int k) { int res = 0; while (k > 0) { res += c[k]; k -= lowbit(k); } return res; } ``` #### 3. 应用场景 - **动态维护逆序对**:通过离散化+树状数组统计右侧小于当前元素的个数[^3] - **区间求和与单点修改**:例如实时统计股票价格波动 - **替代线段树**:在一维且无需复杂区间操作时,代码更简洁[^1] #### 4. 与线段树的对比 | 特性 | 树状数组 | 线段树 | |--------------|---------------|---------------| | 代码复杂度 | 极低(10行内)| 较高(递归或非递归) | | 功能范围 | 单点更新+前缀和 | 支持区间修改/查询 | | 时间复杂度 | $O(\log n)$ | $O(\log n)$ |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值