原理:
有好的博客做讲解了(见参考文章),这里暂时略过,如果以后有新的理解和体会会再来写的。(应该不会)
思想:
这里可以把树状数组的精妙之处提一下(我理解的)
首先,树状数组之所以叫树状数组,因为它像树一样,有类似树的父子节点关系,这点在更新和求和操作上体现的最为明显。而最终也只是数组,因为实现起来简单方便,如数组一样。(一开始还纳闷为什么不叫二进制索引树),英文名BIT(Binary Index Tree)。这个数据结构实现的功能像线段树一样,两者有着异曲同工之妙。
其次,树状数组的神奇之处在于他把数的二进制的关系引入到了数组里,具体的,将数组的下表关系和所包含数的内涵巧妙的建立了联系,同时也引入了树的概念,使得能够在(O(log_2n))的时间内实现修改与查询操作。
然后,基于上一点,树状数组功能强大之处在于求前缀和,要想求区间和还要更新区间和,就不可避免的与差分的思想合二为一,融为一体。最后实现区间查询更是引入了两个差分数组来实现,使得复杂度维持在(O(log_2n))。由此可见,时间的维持以空间作为了代价,往往还有思维上的跨越。
代码:
#include <iostream>
#define max_n 1005
using namespace std;
int a[max_n];//原数组
int c[max_n];//树状数组
int n;//元素个数
int x,y;//更新区间和查询区间
int k;//改变值
int lowbit(int i)//求二进制最后一的位所对应的值(lowbit(8(1000))= 8)
{
return i&(-i);
}
void update(int i,int k)//原始树状数组更新操作
{
while(i<=n)
{
c[i]+=k;
i+=lowbit(i);
}
}
long long sum(int i)//原始树状数组求和操作
{
long long res = 0;
while(i>0)
{
res += c[i];
i-=lowbit(i);
}
return res;
}
void update_1(int i,int k)//引入一次差分树状数组后的更新操作
{
while(i<=n)
{
c[i]+=k;
i+=lowbit(i);
}
}
long long sum_1(int i)//引入一次差分树状数组后的求和操作
{
long long res = 0;
while(i>0)
{
res+=c[i];
i-=lowbit(i);
}
return res;
}
int sum1[max_n];//D[i]
int sum2[max_n];//D[i]*(i-1)
void update_2(int i,int k)//引入两次差分树状数组后的更新操作
{
int x = i;
while(i<=n)
{
sum1[i]+=k;
sum2[i]+=k*(x-1);
i+=lowbit(i);
}
}
long long sum_2(int i)//引入两次差分树状数组后的求和操作
{
long long res = 0;
int x = i;
while(i>0)
{
res += x*sum1[i]-sum2[i];
i-=lowbit(i);
}
return res;
}
int main()
{
//区间更新,单点查询
/*cin >> n;
for(int i = 1;i<=n;i++)
{
cin >> a[i];
update_1(i,a[i]-a[i-1]);
}
cin >> x >> y >> k;//在[x,y]上增加k
update_1(x,k);//差分数组中x位增加k
update_1(y+1,-k);//差分数组中y+1位减少k
cout << sum(2) << endl;//a[2]*/
//区间更新,区间查询
cin >> n;
for(int i = 1;i<=n;i++)
{
cin >> a[i];
update_2(i,a[i]-a[i-1]);
}
cin >> x >> y >> k;
update_2(x,k);//对应位置上两个差分数组的初位置的处理
update_2(y+1,-k);//对应位置上两个差分数组的末位置的处理
cout << sum_2(y)-sum_2(x-1) << endl;//[x,y]的区间和
return 0;
}
参考文章:
好的博客在这里,讲的清楚
Xenny,树状数组详解,https://www.cnblogs.com/xenny/p/9739600.html