树状数组总结

前言:早在知乎看到某位巨巨回答最优美的数据结构:树状数组
原因是:实现简单,代码优雅,效果拔群。
树状状数
这么优美的数据结构,怎么能不来一个总结?

树状数组的作用:频繁对于单点和区间修改和查询操作,时间复杂度都是log(n).

关于原理主要就是维护一个前缀和,各种热心大牛博客都有,百度前几个都解释很好,我就不罗嗦。
这里就发一些题解,作为新手入门训练指南,大牛们可以忽略.
主要操作:
1.追溯其父节点或下辖第一个没有关系的点:(所有操作都是围绕这个)

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

2.修改元素的值

void update(int i,int x){
     while(i<=n){
        tree[i]+=x;
        i+=lowbit(i);
     }
}

3.求数组前n项和

int Querty(int x){
    int sum=0;
    while(x>0){
      sum+=tree[x];
      x-=lowbit(x);
    }
    return sum;
}

##常见题目类型:

1.单点更新与区间求和

hdu1166 敌兵布阵
题意:给你一串数,然后会根据题意选择一点增加或减少,或者询问某区间的人数有多少?
这题算是线段树,树状数组模板题,操作就是围绕单点修改,区间求和。

#include<bits/stdc++.h>
using namespace std;
int c[50010],n;
int lowbit(int x){
    return x&(-x);
}

void update(int x,int num){
    while(x<=n){
        c[x]+=num;
        x+=lowbit(x);
    }
}

int getsum(int x){
    int s=0;
    while(x>0){
        s+=c[x];
        x-=lowbit(x);
    }
    return s;
}

int main(){
    int T;
    scanf("%d",&T);
    for(int cur=1;cur<=T;cur++){
        memset(c,0,sizeof(c));
        printf("Case %d:\n",cur);
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            int x;
            scanf("%d",&x);
            update(i,x);
        }
        char s[10];int a,b;
        //scanf("%s",s);
        while(scanf("%s",s)!=EOF&&strcmp(s,"End")){
            scanf("%d %d",&a,&b);
            if(s[0]=='Q')
                printf("%d\n",getsum(b)-getsum(a-1));
            else if(s[0]=='A')
                update(a,b);
            else
                update(a,-b);
        }
    }
    return 0;
}

2.区间更新,单点求值。

hdu1556 color the ball
这题给了一个新思路:树状数组不仅可以求单点更新区间求和,还可以求区间更新,单点求和。
在单点更新里面,树状数组求和操作代表区间的和,而在区间更新中,树状数组求和操作代表单个元素的变化量。

所以这题比如对(4,6)区间进行更新,就是update(4,1),update(6+1,-1),
Query(i)代表查询第i个数变化量。

#include<bits/stdc++.h>
using namespace std;
int tree[100010],n;
void init(){
    memset(tree,0,sizeof(tree));
}

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

void update(int x,int num){
    while(x<=n)
    {
        tree[x]+=num;
        x+=lowbit(x);
    }
}

int getsum(int x){
    int sum=0;
    while(x>0) {
        sum+=tree[x];
        x-=lowbit(x);
    }
    return sum;
}

int main(){
    while(~scanf("%d",&n)&&n) {
        init();
        for(int i=1;i<=n;i++){
            int a,b;
            scanf("%d %d",&a,&b);
            update(a,1);
            update(b+1,-1);
        }
        for(int i=1;i<=n-1;i++)
         printf("%d ",getsum(i));
        printf("%d\n",getsum(n));
        for(int i=1;i<=n;i++) cout<<tree[i]<<endl;
    }
}

3.逆序数问题

在一个排列里,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么我们就称它为一对逆序数。树状数组可以求一个排列里逆序数的对数,分治归并也可以解决。
hdu 2689 Sort it
题意:把一个具有n个不同元素的序列,只能交换两个相邻的位置的元素,问最少交换多少次变成升序序列。
思路:对于当前大小为a的第i个元素,他需要交换的次数为前面比它大的数的个数,那么整个序列需要交换次数为逆序对数。
逆序数求法:tree[i]值为0或1表示i这个数出现的是否出现,

#include<bits/stdc++.h>
using namespace std;
int a[1010],n;
int lowbit(int x)
{
    return x&(-x);
}

void update(int i,int x)
{
    while(i<=n){
        a[i]+=x;
        i+=lowbit(i);
    }
}
int Query(int x)
{
    int sum=0;
    while(x>0){
        sum+=a[x];
        x-=lowbit(x);
    }
    return sum;
}

int main()
{
    while(cin>>n){
        memset(a,0,sizeof(a));
        int ans=0;
        for(int i=1;i<=n;i++){
            int x;cin>>x;
            update(x,1);
            ans+=i-Query(x);
        }
        cout<<ans<<endl;
    }
}

变形一下的逆序数:
hdu2838 Cow Sorting
求一个序列里面所有逆序数对之和,对于序列大小为a的第i个数,除了知道他前面有多少个比他小的数,还要知道他们的和,所以这里需要结构体记录个数,和之和两个信息。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=100000+10;
int n;
struct node
{
    int cnt;
    long long sum;
}tree[N];
int lowbit(int x) {return x&(-x);}
void update(int x,int v,int cnt){
    while(x<=n){
        tree[x].cnt+=1;
        tree[x].sum+=v;
        x+=lowbit(x);
    }
}
long long get_cnt(int x){
    long long sum=0;
    while(x>0){
        sum+=tree[x].cnt;
        x-=lowbit(x);
    }
    return sum;
}
long long get_sum(int x){
    long long sum=0;
    while(x>0){
        sum+=tree[x].sum;
        x-=lowbit(x);
    }
    return sum;
}
int main()
{
    while(~scanf("%d",&n)){
        memset(tree,0,sizeof(tree));
        long long sum=0;
        for(int i=1;i<=n;i++){
            int a;scanf("%d",&a);
            update(a,a,1);
            int t=i-get_cnt(a);
            //cout<<"t="<<t<<endl;
            if(t!=0){
                sum+=((long long)t*a+get_sum(n)-get_sum(a));
            }
        }
        cout<<sum<<endl;
    }
}

4.多维树状数组。
二维树状数组:hdu2642 Stars
题意:在一个平面区域里统计出现星星个数。
思路:多维树状数组在统计区域值得时候,通常需要容斥原理求得特定区域的信息,这题也是.
注意这题星星可能多次点亮操作,所有需要一个标记数组。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1000;
int tree[N+10][N+10];
bool mark[N+10][N+10];
int lowbit(int x){return x&(-x);}
void update(int x,int y,int num){
   for(int i=x;i<=N;i+=lowbit(i)){
    for(int j=y;j<=N;j+=lowbit(j)){
        tree[i][j]+=num;
    }
   }
}

int Query(int x,int y){
    int sum=0;
    for(int i=x;i>0;i-=lowbit(i)){
        for(int j=y;j>0;j-=lowbit(j)){
            sum+=tree[i][j];
        }
    }
    return sum;
}

int main()
{
    int n;
    while(~scanf("%d",&n)){
        memset(tree,0,sizeof(tree));
        memset(mark,false,sizeof(mark));
        for(int i=0;i<n;i++){
            char ch;scanf(" %c",&ch);
            if(ch=='B'){
                int x,y;scanf("%d%d",&x,&y);
                x++,y++;
                if(!mark[x][y]) update(x,y,1),mark[x][y]=true;
            }
            else if(ch=='Q'){
                int x1,y1,x2,y2;scanf("%d%d%d%d",&x1,&x2,&y1,&y2);
                x1++;y1++;x2++;y2++;
                if(x1<x2) swap(x1,x2);
                if(y1<y2) swap(y1,y2);
                printf("%d\n",Query(x1,y1)-Query(x1,y2-1)-Query(x2-1,y1)+Query(x2-1,y2-1));
            }
            else if(ch=='D'){
                int x,y;scanf("%d%d",&x,&y);
                x++;y++;
                if(mark[x][y]) update(x,y,-1),mark[x][y]=false;
            }
        }
    }
}

三维树状数组:hdu3584
这题在三维空间,并且是区域更新,点查询。
然后区域更新的时候运用容斥原理(找规律的排列一下参数)。

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int tree[N][N][N];
int lowbit(int x){return x&(-x);}
void update(int x,int y,int z){
   for(int i=x;i<=N;i+=lowbit(i)){
    for(int j=y;j<=N;j+=lowbit(j)){
        for(int k=z;k<=N;k+=lowbit(k)){
            tree[i][j][k]=(tree[i][j][k]== 0 ? 1 : 0);
        }
    }
   }
}
int Query(int x,int y,int z){
   int sum=0;
   for(int i=x;i>0;i-=lowbit(i)){
    for(int j=y;j>0;j-=lowbit(j)){
        for(int k=z;k>0;k-=lowbit(k)){
            sum+=tree[i][j][k];
        }
    }
   }
   return sum;
}

int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        memset(tree,0,sizeof(tree));
        for(int i=0;i<m;i++){
             int a[4];cin>>a[3]>>a[0]>>a[1]>>a[2];
             if(a[3]==1){
                int b[3];cin>>b[0]>>b[1]>>b[2];
                update(a[0],a[1],a[2]);
                update(a[0],a[1],b[2]+1);
                update(a[0],b[1]+1,a[2]);
                update(b[0]+1,a[1],a[2]);
                update(a[0],b[1]+1,b[2]+1);
                update(b[0]+1,a[1],b[2]+1);
                update(b[0]+1,b[1]+1,a[2]);
                update(b[0]+1,b[1]+1,b[2]+1);
             }
             else{
                printf("%d\n",Query(a[0],a[1],a[2])%2);
             }
        }
    }
}

另外常见出题形式:
1.离散化化树状数:
poj2299
思路:常见 题目数据多大,树状数组就开多大,然而有的题单个数据很大,但总共个数不大,我们就可以对其离散化。比如这题:数据范围999999999,n范围十万.求逆序数对数。
离散化步骤:存最初每个数的顺序,按照大小排序,后,按照排序后的顺序和原来位置重新赋值。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define N 500001
struct node
{
    int v,pos;
}a[N];
int tree[N],c[N];
bool cmp(node a,node b){ return a.v<b.v;}

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

void update(int x){
   while(x<=N){
     tree[x]+=1;
     x+=lowbit(x);
   }
}

long long get_sum(int x)
{
    long long sum=0;
    while(x>0){
        sum+=tree[x];
        x-=lowbit(x);
    }
    return sum;
}

int main()
{
    int n;
    while(scanf("%d",&n)&&n){
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i].v);
            a[i].pos=i;
        }
        sort(a+1,a+n+1,cmp);
        for(int i=1;i<=n;i++) c[a[i].pos]=i;
        memset(tree,0,sizeof(tree));
        long long ans=0;
        for(int i=1;i<=n;i++){
            update(c[i]);
            ans+=(i-get_sum(c[i]));
        }
        printf("%lld\n",ans);
    }
}

2.求区间最值问题:
当然区间最值问题,最优做法是线段树,灵活,信息量大。但是树状数组代码更少不是吗?
常见借用树状数组解决区间最值问题有三种方法:
1.用二进制表示:
转自:http://www.cnblogs.com/ambition/archive/2011/04/06/bit_rmq.html


    #include <iostream>  
    using namespace std;  
      
    #define maxn 1<<20  
    int n,k;  
    int c[maxn];  
      
    int lowbit(int x){  
        return x&-x;  
    }  
      
    void insert(int x,int t){  
           while(x<maxn){  
              c[x]+=t;  
              x+=lowbit(x);  
           }  
    }  
    int find(int k){  
        int cnt=0,ans=0;  
        for(int i=20;i>=0;i--){  
            ans+=(1<<i);  
            if(ans>=maxn || cnt+c[ans]>=k)ans-=(1<<i);  
            else cnt+=c[ans];  
        }  
        return ans+1;  
    }  
    void input(){  
           memset(c,0,sizeof(c));  
           int t;  
           scanf("%d%d",&n,&k);  
           for(int i=0;i<n;i++){  
                scanf("%d",&t);  
                insert(t,1);  
           }  
           printf("%d\n",find(k));  
    }  
    int main(){  
        int cases;  
        scanf("%d",&cases);  
        while(cases--){  
            input();  
        }  
        return 0;  
    }  

2.区间划分
转自:http://www.cnblogs.com/ambition/archive/2011/04/06/bit_rmq.html

3.二分枚举:
hdu2852

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
int tree[N+10];
int lowbit(int x) {return x&(-x);}
void add(int i,int x)
{
  while(i<=N){
     tree[i]+=x;
     i+=lowbit(i);
  }
}

int get_sum(int i)
{
    int sum=0;
    while(i>0){
        sum+=tree[i];
        i-=lowbit(i);
    }
    return sum;
}

int binary(int pos,int k)
{
   int l=pos+1,r=N,x=get_sum(pos),mid=N,ans=N;
   while(l<=r){
     mid=(l+r)*0.5;
     if(get_sum(mid)-x>=k){
         r=mid-1;
         ans=mid;//这里注意,(l+r)/0.5取得是下限,比如正确结果是4,l=3,r=4时,虽然进不来,mid会变成3.所以结果保存能满足的mid.
     }
     else l=mid+1;
   }
   return ans;
}

int main()
{
   int n;
   while(~scanf("%d",&n)){
     memset(tree,0,sizeof(tree));
     for(int i=0;i<n;i++){
         int p;scanf("%d",&p);
         switch(p){
             case 0:{
                 int e;scanf("%d",&e);
                 add(e,1);
                 break;
             }
             case 1:{
                 int e;scanf("%d",&e);
                 if(get_sum(e)-get_sum(e-1)>0) add(e,-1);
                 else printf("No Elment!\n");
                 break;
             }
             case 2:{
                 int a,k;scanf("%d%d",&a,&k);
                 int ans=binary(a,k);
                 if(ans==N) printf("Not Find!\n");
                 else printf("%d\n",ans);
                 break;
             }
         }
     }
   }
}

  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值