树状数组

题目
蒜头君经营着一个不大的水果店。他认为生存之道就是经营最受顾客欢迎的水果。现在他想要一份水果销售情况的明细表,这样就可以很容易掌握所有水果的销售情况了。

蒜头君告诉你每一笔销售记录的水果名称,产地和销售的数量,请你帮他生成明细表。

输入格式

第一行是一个整数 N(0<N≤1000) N(0 < N \le 1000)N(0<N≤1000),表示共有 N NN 次成功的交易。

其后有 N NN 行数据,每行表示一次交易,由水果名称(小写字母组成,长度不超过 100 100100),水果产地(小写字母组成,长度不超过 100 100100)和交易的水果数目(正整数,不超过 1000 10001000)组成.

输出格式

请你输出一份排版格式正确(请分析样本输出)的水果销售情况明细表。这份明细表包括所有水果的产地、名称和销售数目的信息。水果先按产地分类,产地按字母顺序排列;同一产地的水果按照名称排序,名称按字母顺序排序。

样例输入

5

apple shandong 3

pineapple guangdong 1

sugarcane guangdong 1

pineapple guangdong 3

pineapple guangdong 1

样例输出

guangdong

   |----pineapple(5)

   |----sugarcane(1)

shandong

   |----apple(3)


树状数组
最低端的叶子储存数组A[1]~A[8]
在这里插入图片描述
现在用上一级的叶子储存C[]数组
在这里插入图片描述
C[i]代表 子树的叶子结点的权值之和

C[1]=A[1];

C[2]=A[1]+A[2];

C[3]=A[3];

C[4]=A[1]+A[2]+A[3]+A[4];

C[5]=A[5];

C[6]=A[5]+A[6];

C[7]=A[7];

C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
在这里插入图片描述
将C[]数组的结点序号转化为二进制

1=(001) C[1]=A[1];

2=(010) C[2]=A[1]+A[2];

3=(011) C[3]=A[3];

4=(100) C[4]=A[1]+A[2]+A[3]+A[4];

5=(101) C[5]=A[5];

6=(110) C[6]=A[5]+A[6];

7=(111) C[7]=A[7];

8=(1000) C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
对照式子可以发现 C[i]=A[i-2k+1]+A[i-2k+2]+…A[i]; (k为i的二进制中从最低位到高位连续零的长度)
lowbit(x) 其实就是求出x的二进制最低位1 换言之 lowbit(x)=2^k k的含义与上面相同 (^在计算机中代表异或运算符)

int lowbit(int t)
{
return t&(-t);
}

程序运行时,数据用的都是补码
如x=1 1&-1(设位为8位)0000 0001 & 1111 1111 = 1 求出2^p(其中p: x 的二进制表示数中, 右向左数第一个1的位置),如6的二进制表示为110,向左数第零个为0,第一个为1,则p=1,故Lowbit(6) = 2^1 = 2。

区间查询
可以利用C[i]数组,求A数组中前i项的和
举个例子 i=7;
sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ; 前i项和

C[4]=A[1]+A[2]+A[3]+A[4];

C[6]=A[5]+A[6]; C[7]=A[7];

可以推出: sum[7]=C[4]+C[6]+C[7];

序号写为二进制: sum[(111)]=C[(100)]+C[(110)]+C[(111)];
发现下标的位运算关系100&110&111=111
细细观察二进制 树状数组追其根本就是二进制的应用
结合代码

int getsum(int x)
{
int ans=0;
for(int i=x;i>0;i-=lowbit(i))//i的步长lowbit(i))
ans+=C[i];
return ans;
}

对于i=7 进行演示

7(111) ans+=C[7]

lowbit(7)=001 7-lowbit(7)=6(110) ans+=C[6]

lowbit(6)=010 6-lowbit(6)=4(100) ans+=C[4]

lowbit(4)=100 4-lowbit(4)=0(000)
则sum[7]=C[7]+C[6]+C[4]
这里发现其规律是讲1一位一位的置为0

单点更新

修改A[]数组中的某一个值时 应当如何更新C[]数组呢?

结合代码分析

void add(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i))
tree[i]+=y;
}

//n是树的最顶端,通过累加的方式从底层像上更新,并且因为树的关系i的步长是lowbit(i)
//可以发现 更新过程是查询过程的逆过程
//由叶子结点向上更新C[]数组当我们

回想一下 区间查询的过程,再看一下上文中列出的图

在这里插入图片描述
如图:

当更新A[1]时 需要向上更新C[1] ,C[2],C[4],C[8]

                 C[1],   C[2],    C[4],     C[8]

写为二进制 C[(001)],C[(010)],C[(100)],C[(1000)]

                                  1(001)        C[1]+=A[1]

lowbit(1)=001 1+lowbit(1)=2(010) C[2]+=A[1]

lowbit(2)=010 2+lowbit(2)=4(100) C[4]+=A[1]

lowbit(4)=100 4+lowbit(4)=8(1000) C[8]+=A[1]

底层的数据更新后上层储存的和值也应该相应更新;

总结

树状数组的储存方式之所以与二进制有关是因为树的储存方式是一种天然的二进制,每一个节点下的左叶与右叶就是天然的二进制和是与非一样,所以在计算机下用树储存的数组进行查找和运算会更快,像上述的例子进行求和的运算,是预先对n个数据进行预运算,所以当输入摸个值的累加时会更节省时间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值