【树状数组(一)】

树状数组,又称二进制索引树,英文名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

http://hawstein.com/posts/binary-indexed-trees.html

http://blog.csdn.net/zxy_snow/article/details/6264135

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值