树状数组,又称二进制索引树,英文名Binary Indexed Tree。
一、树状数组的用途
主要用来求解数列的前缀和,a[0]+a[1]+...+a[n]。
由此引申出三类比较常见问题:
1、单点更新,区间求值。(HDU1166)
2、区间更新,单点求值。(HDU1556)
3、求逆序对。(HDU2838)
二、树状数组的表示
1、公式表示
设A[]为一个已知的数列。C[]为树状数组。则会有
C[i]=A[j]+...+A[i];j=i&(-i)=i&(i^(i-1))。
2、图形表示
(注:1、最下面的一行表示数组A,上面的二进制表示的部分是C;
从以上可以发现:
1、树状数组C是表示普通数组A的一部分的和。
2、小标为奇数时,C[i]只能管辖一个A[i]。
3、C[i]的最后一个数一定是A[i]。
一维树状数组常用的3个函数
int lowbit(int x) //取x的最低位1,比如4,则返回4,如5,则返回1
{
return x&(-x);
}
void update(int i, int val) //将第i个元素增加val
{
//i的祖先都要增加val
while(i <= n)
{
sum[i] += val;
i += lowbit(i); //将i的二进制未位补为得到其祖先
}
}
int Sum(int i) //求前i项的和
{
int s = 0;
//将前i项分段
while(i > 0)
{
s += sum[i];
i -= lowbit(i); //去掉i的二进制最后一个
}
return s;
}
/*
HDU 1166
单点更新,区间求值
*/
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=50001;
int a[maxn],c[maxn];
int n;
int lowbit(int t){ //取x的最低位1,比如4,则返回4,如5,则返回1
return t&(-t);
}
void modify(int t,int num){ //将第t个元素增加val
while(t<=n){
c[t]+=num;
t+=lowbit(t); //将t的二进制未位补为得到其祖先
}
}
int getresult(int t){ //求前t项的和
int sum=0;
while(t>0){
sum+=c[t];
t-=lowbit(t); //去掉t的二进制最后一个
}
return sum;
}
void init(){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
modify(i,a[i]);
}
}
int main(){
int test,k=1;
scanf("%d",&test);
while(test--){
memset(c,0,sizeof(c));
printf("Case %d:\n",k++);
scanf("%d",&n);
init();
char ch[15];
int a,b;
while(scanf("%s",&ch),strcmp(ch,"End")){
scanf("%d%d",&a,&b);
switch(ch[0]){
case 'Q':
printf("%d\n",getresult(b)-getresult(a-1));
break;
case 'A':
modify(a,b);
break;
case 'S':
modify(a,-b);
break;
}
}
}
}
/*
HDU1556
区间更新,单点求值
*/
#include<cstdio>
#include<cstring>
using namespace std;
int c[100005];
int n;
int lowbit(int x){
return x&(-x);
}
void updata(int i,int num){
while(i<=n){
c[i]+=num;
i+=lowbit(i);
}
}
int getsum(int i){
int sum=0;
while(i>0){
sum+=c[i];
i-=lowbit(i);
}
return sum;
}
int main(){
int a,b;
while(scanf("%d",&n),n){
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++){
scanf("%d%d",&a,&b);
updata(a,1);//大于a的点都加1
updata(b+1,-1);//然后把大于b+1的点都减一
} //得到的就是a到b都加1.
for(int j=1;j<n;j++)
printf("%d ",getsum(j));
printf("%d\n",getsum(n));
}
}
/*
题意:给你N个排列不规则的数,任务是把它从小到大排好,每次只能交换相邻两个数,交换一次的代价为两数之和,求最小代价
思路:求解比a小的个数
然后求解比a小的个数的总和
然后所有数的和,
*/
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=100001;
struct node{
int cnt;
long long sum;
}tree[maxn];
int n;
int lowbit(int x){
return x&(-x);
}
void modify(int x,int y,int t){
while(x<=n){
tree[x].sum+=y;
tree[x].cnt+=t; //tree[].cnt来保存a出现的次数
x+=lowbit(x);
}
}
long long query_cnt(int x){ //比x小的数的个数
long long sum=0;
while(x>0){
sum+=tree[x].cnt;
x-=lowbit(x);
}
return sum;
}
long long query_sum(int x){ //比x小的所有数之和
long long sum=0;
while(x>0){
sum+=tree[x].sum;
x-=lowbit(x);
}
return sum;
}
int main(){
while(cin>>n){
int a;
long long ans=0;
memset(tree,0,sizeof(tree));
for(int i=1;i<=n;i++){
cin>>a;
modify(a,a,1); //以a为下标更新数组
long long k1=i-query_cnt(a); //k1为前i个数比a大的数的个数
if(k1!=0){
long long k2=query_sum(n)-query_sum(a); //目前所有数的和-目前所有比a小的数的和,为比a大的数的和
ans+=k1*a+k2; //调换a所需的时间
}
}
cout<<ans<<endl;
}
}
http://blog.csdn.net/maiyuetong/article/details/6661496
http://blog.csdn.net/lulipeng_cpp/article/details/7816527