树状数组推荐看训练指南的说明,90%能看懂。不能看懂再往下看。
求a1,a2...an的和,下面看 CS 97SI: Introduction to Competitive Programming Contests 的PPT里的图片。
思想直接看原文:
Each internal node stores the sum of values of its children
– e.g., Red node stores item[5] + item[6]
Main idea: choose the minimal set of nodes whose sum gives the desired value
I We will see that– at most 1 node is chosen at each level so that the total number of nodes we look at is log 2 n
– and this can be done in O(logn) time
计算方法
//版本2
树状数组是对数组元素修改和查询都是O(logn)的结构,主要用于查询任意两位之间的所有元素之和,可参考百度
最基本的是在有修改元素的情况下求区间内元素和
int lowbit(int t)
{
return t&(-t);
}
lowbit(i):将i转化成二进制数之后,只保留最低位的1及其后面的0,截断前面的内容,然后再转成10进制数
X^:X取反(符号我这里定的,不是什么官方符号)
lowbit(x)实际上是提取x从左往右数的最后一个1。
设x为a1b,a1b中的1位最后一个1,a和b都表示一串01串(当然b全为0或者不存在),如13=1101则a=110,b不存在。
对一个数x取负相当于该数的二进制取反再加1,所以-x = (a^- 1^ b^) = (a^ 0 1...1) +1 = (a^ 1 0...0)。
则 x & (-x) = (a 1 00...) & (a^ 1 00...) = (0...0 1 0...0)。结果就是保留最后个1和后面的0的十进制答案。
如
10进制值 | 二进制值 | lowbit()值 |
2 | 10 | 2 |
3 | 11 | 1 |
4 | 100 | 4 |
5 | 101 | 1 |
那么 i + lowbit( i ) 可以求出父节点的下标
把子树向右对称翻得出一些空白结点,树变成完全二叉树,看图可知父节点下标和空白结点下标相同
i | lowbit(i) | i+lowbit(i) |
2 | 2 | 4 |
3 | 1 | 4 |
4 | 4 | 8 |
5 | 1 | 6 |
像 i == 5时,父节点下标是6,表示c[5]在c[6]管辖下,c[6]还管了个A[6],c[6] = c[5] + a[6]
void add(int i,int num)
{
while(i <= n)
{
c[i] += num;
i += lowbit(i);
}
}
给a[ i ]加值,则要把c[ i ] 和 管理c[ i ]的那些 c[ x ] 也加上相同的值,如给1 号元素加1,则要把c[1],c[2],c[4],c[8]都加1。add()代码中 i += lowbit(I)就是父节点下标
那么减值就是参数为负数就行
int getSum(int i)
{
int sum = 0;
while(i > 0)
{
sum += c[i];
i -= lowbit(i);
}
return sum;
}
i - lowbit( i ) 求出跟 另一个c[ x ] ,c[ x ] 管理了 小于 i 的 a [ i ] 中,c[ i ] 所没有管理到的 a[ i ],那么把c [ i ] 和 c [ x ]加起来就是A[1]...A[ i ]的和
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
i - lowbit(i) | 0 | 0 | 2 | 0 | 4 | 4 | 6 | 0 |
还不过瘾就看看http://www.hawstein.com/posts/binary-indexed-trees.html吧,是Topcoder文章的翻译
这种直接插入求和,修改值的函数( 这里的add() )的循环是从下往上,而求和函数是从上往下,所以也被归类为 向上查询,向下求和的类型
下面来道向下查询,向上求和的题目
题目很好理解,代码实现不需要a数组。但add()一改就是改一串相关联的,那么要改区间[a,b]元素的值,可以这样:把[1,b]元素值+1,再把[1,a-1]值-1,抵消后就是把[a,b]的元素值+1,因为改的是小于等于 i 的部分值,所以是向下查询
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define FOR(i,n) for(int i=0;i<(n);i++)
#define ll __int64
#define NMAX 100001
using namespace std;
int c[NMAX],n;
int lowbit(int t)
{
return t&(-t);
}
void add(int i,int num)
{
while(i > 0)
{
c[i] += num;
i -= lowbit(i);
}
}
int getSum(int i)
{
int sum = 0;
while(i <= n)
{
sum += c[i];
i += lowbit(i);
}
return sum;
}
int main()
{
// freopen("in.txt","r",stdin);
int a,b;
while(scanf("%d",&n)!=EOF,n)
{
memset(c,0,sizeof(c));
for(int i=0;i<n;i++)
{
scanf("%d%d",&a,&b);
add(b,1);
add(a-1,-1);
}
printf("%d", getSum(1));
for(int i=2;i<=n;i++)
{
printf(" %d",getSum(i));
}
printf("\n");
}
return 0;
}
HDU 1166 敌兵布阵 此题还有线段树解法
#include <cstdio>
#include <cstring>
#define INF 0x7FFFFFFF
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define CLR( a , x ) memset ( a , x , sizeof (a) )
#define RE freopen("1.in","r",stdin)
#define LL(i) (i<<1)
#define RR(i) (i<<1|1)
#define MAX 50010
int ans;
int a[MAX];
int lowbit(int x)
{
return x&(-x);
}
int sum(int i)
{
int ans = 0;
while(i > 0)
{
ans += a[i];
i -= lowbit(i);
}
return ans;
}
void add(int i,int num)
{
while(i <= MAX)
{
a[i] += num;
i += lowbit(i);
}
}
int main()
{
RE;
int n,m,l,r,t,x;
char cmd[10];
scanf("%d",&t);
FOR(tt,1,t)
{
CLR(a,0);
printf("Case %d:\n",tt);
scanf("%d",&n);
FOR(i,1,n)
{
scanf("%d",&x);
add(i,x);
}
getchar();
while(scanf("%s",cmd),cmd[0]!='E')
{
switch(cmd[0])
{
case 'Q':
scanf("%d%d",&l,&r);
ans = sum(r) - sum(l-1);
printf("%d\n",ans);
break;
case 'A':
scanf("%d%d",&l,&r);
add(l,r);
break;
case 'S':
scanf("%d%d",&l,&r);
add(l,-r);
break;
}
}
}
return 0;
}