树状数组理解和POJ2155二维树状数组参考算法理解

本文深入浅出地介绍了树状数组的基本概念及其在一维和二维场景下的应用。详细解析了一维树状数组如何高效处理数组元素的动态更新及前缀和查询,并通过实例展示了二维树状数组解决矩阵操作问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

读者要求:知道什么是树状数组,不知道可以先看一下百度百科,然后再来阅读本文,传送门:

https://baike.baidu.com/item/%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84/313739?fr=aladdin

首先谈一下个人对于一维树状数组的理解:

    

       首先树状数组是为了解决频繁的读取前k项的数组和而且数组内容动态改变的问题而提出的(个人认为),这里强调一下数组内容动态改变。假如数组的内容是静态的,那么只需要一个数组D,第K个元素的内容就是数组A前K个元素的和就可以了。但是考虑到数组内容的动态改变,假设改变数组A第K个元素,那么对应的上面的D数组K后面的元素便都要更新,比较费时。可以考虑改变D数组的内容编码,这样就可以减少当A数组元素更新时需要更新的D数组的元素个数。比如按照上面的树状数组编码方式,C数组,当改变A6时,只需要更新C6和C8两个元素,而D数组要更新D6,D7,D8,D9四个元素。虽然求和时需要更多的C数组的元素表示,比如求前6项的和,按照D数组,只需要D6就可以,但是按照C树状数组,需要C6+C4。但是对于频繁改变内容的数组还是树状数组比较好

        下面讲一下对于s[x]=a[1]+a[2]+a[3]+...+a[k];为什么可以使用如下代码计算:

s[x]=0;
for(int =x;i>0;i-=lowbit(i))s[x]+=c[i];

上面c[k]=a[k-lowbit(k)+1]+...+a[k],lowbit(k)=k&(-k)就是整数k的二进制表示中右边第一个1所代表的数字(不知道请百度)

首先从目的出发,我们是要求前x项的和,为了方便理解,我就求前6项的和:s[6]。我们可以把6写成二进制来观察,即110,

110有什么特殊的含义呢?对!他可以分成1*2^4+1*2^1(哈哈),现在求和就变成了两个部分,之所以是两个部分是因为二进制里面有2个1(可以这样理解):(A1+A2+A3+A4)+(A5+A6),我们称前一部为X,后一部分为Y,现在的问题是如何理解这两个部分,这两个部分和C数组有什么关系。对于X部分或者Y部分,我们描述他们需要2个参数,一个是最右边的元素的下标,就是X部分里面的A4和Y部分里面的A6,然后就是每一个部分的长度,就是X的4个长度和Y的2个长度,机智的读者已经发现其实Y部分的和就是C[6],X部分的和即使C[4],换句话说假如我们求和s[x],他可以分成若干C的和,具体的C的元素确定可以通过对x写成二进制,然后观察里面的1,1的个数代表了和被分成几部分,也就是C的个数,然后我们可以从最后一个元素即Ax开始推,就举上面的例子,首先从A6开始,第一部分和是C6,他的长度是0010,就是2,这个就是6二进制右边第一位1的数值,可以通过lowbit计算,之所以需要知道C6跨越的长度是为了知道下一个C是多少,6-2=4,所以下一个开始的起点是A4,和是C4,这一个步进就是i-=lowbit(i)了,现在上面的代码应该很清楚了吧(我认为是)。

     下面讲一下具体的一个元素,他的父节点下标的计算或者遍历吧,下面是代码:

for(int i=k;i<n;i+=lowbit(i))

首先通过上面的介绍,对于C[k]可以理解为一个区间,就是(A[k-lowbit(k)+1,A[k]],比如C[6]=(A[5],A[6]](注意左端点取不到)现在的问题就是包含Ax的区间有哪些,也即是C有哪些,或者讲父亲元素有哪些。考虑x的二进制举个例子进行讲解吧,求A6的父元素的下标,6的二进制0110,显然比6小的C是不可能包含A6的,那么现在观察大于6的二进制数,我们这样考虑,分成2部分,一部分是6第一个1的右边部分,即0110和左边0110。我们先保证左边部分不变,对右边部分的部分0变成1,显然只要右边部分有一个1,对应的那个C的下界是0110,而且取不到,故在右边部分是不可以有1的,就是说0111是不行的,那么下一个数字就是1000了,显然C8是OK的,从图中可以看出,是第一个父元素,上面的结论规范下就是对于一个x,写成二进制,比如1000010000,对于第一个右边的1右边不能出现1,就是1000010001到1000011111都是不行的,至少加10000,得到1000100000,这个C是可以的,下面推下一个C,对于1000100001到1000111111也是不包含1000010000的,至少要加100000,才可以,就这样递推下去就可以求完所有包含Ax的C了,这就是为什么每次步进i+=lowbit(i)的原因了。

现在讲一下POJ 2155的题目了,楼教主出的题目,传送门:

http://poj.org/problem?id=2155

 代码如下:

int N;
int tree[1005][1005]//01矩阵
void Getsum(int x,int y)//对子矩阵(1,1)-(x,y)进行置反
{
   for(int i=x;i>0;i-=(i&-i))
       {
           for(int j=y;j>0;j-=(j&-j))
              {
                  tree[i][j]^=1;
              }
       } 
} 
int Update(int x,int y)//计算和返回(x,y)的值
{
   int s=0;
   for(int i=x;i<=N;i+=(i&-i))
    {
       for(int j=y;j<=N;j+=(j&0j))
          {
             s+=tree[i][j];
          }
    }
     if(s%2==0)return 0;
     else return 1;
}

这里我只写了关键的两个函数,分别是进行置换的函数Getsum()和求(x,y)值的函数Update(),由于这种解法是使用二维树状矩阵的,我就解释一下上面两个函数,这里的tree矩阵不是原来的01矩阵,也就是说不是题目上的01矩阵,而是题目上的树状矩阵,比如上面文中的原来数组A,C数组是A上的树状数组一样,里面的元素是对应A数组里面的元素的和,这里的tree矩阵就是二维树状矩阵,他同样管理着一块区域,这里管理可以这样理解。比如一维树状数组里面的C6是A6和A5的和,可以理解为C6管理A6到A5这一块区域,同样tree[x][y]也是管理以前区域,其中x为[x-lowbit(x)+1,x],y为[y-lowbit(y)+1,y],下面举个例子,考虑3*3的矩阵

sum[3][3]=tree[3][3]+tree[3][2]+tree[2][3]+tree[2][2],注意这里其实不是加,加号只是表示一下tree[3][3]的区域由右边的4块区域组成,蓝色是tree[3][3],黄色是tree[3][2],红色是tree[2][3],紫色是tree[2][2],所以反置3*3内的矩阵就是分别反置上面四块区域的矩阵,即更新tree[3][3],tree[2][3],tree[3][2],和tree[2][2]即可,这就是上面Getsum函数的意思。现在求(x,y)出的值,只需要考虑包含(x,y),的区域反置次数的总和是奇还是偶就可以了,这句话可以这样理解,首先(x,y)有可能位于多个tree包含的面积下,而反置操作只记录在tree里面,1表示反置了奇数次,0相当于没有反置,假设包含(x,y)的tree有n1,n2,n3,n1是1,n2是1,n3是0,就是说一共对(x,y)反了2次所以(x,y)还是0,奇数就是1。这个就是Update()函数的工作,他就是求(x,y)的父节点,原理其实和上面的一维树状数组一样,而Getsum反置和一维的求前k个元素的和是一样的,当然这里的tree里面的值不再是和,而是反置的次数,奇数次是1,偶数次是0。讲完了!!!!

内容概要:本文档主要展示了C语言中关于字符串处理、指针操作以及动态内存分配的相关代码示例。首先介绍了如何实现键值对(“key=value”)字符串的解析,包括去除多余空格根据键获取对应值的功能,并提供了相应的测试用例。接着演示了从给定字符串中分离出奇偶位置字符的方法,并将结果分别存储到两个不同的缓冲区中。此外,还探讨了常量(const)修饰符在变量指针中的应用规则,解释了不同类型指针的区别及其使用场景。最后,详细讲解了如何动态分配二维字符数组,并实现了对这类数组的排序与释放操作。 适合人群:具有C语言基础的程序员或计机科学相关专业的学生,尤其是那些希望深入理解字符串处理、指针操作以及动态内存管理机制的学习者。 使用场景及目标:①掌握如何高效地解析键值对字符串并去除其中的空白字符;②学会编写能够正确处理奇偶索引字符的函数;③理解const修饰符的作用范围及其对程序逻辑的影响;④熟悉动态分配二维字符数组的技术,并能对其进行有效的排序清理。 阅读建议:由于本资源涉及较多底层概念技术细节,建议读者先复习C语言基础知识,特别是指针内存管理部分。在学习过程中,可以尝试动手编写类似的代码片段,以便更好地理解掌握文中所介绍的各种技巧。同时,注意观察代码注释,它们对于理解复杂逻辑非常有帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值