【树状数组(一)】

来源:互联网 发布:地方门户cms 编辑:程序博客网 时间:2024/06/05 20:40

树状数组,又称二进制索引树,英文名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的祖先都要增加valwhile(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

0 0