树状数组应用总结

前言

做个总结,忘记之后再翻翻

首先明确一下树状数组的结构性质:

1.每个内部节点c[x]c[x]保存以它为根的子树中所有叶节点的和

2.每个内部节点c[x]c[x]的子节点个数等于lowbit(x)lowbit(x)的位数,即位为1的数量

例如:
x=9=23+21x=9=2^3+2^1,分为[1,8],[9,9][1,8],[9,9]两个区间,区间和分别为c[8],c[9]c[8],c[9]
x=13=23+22+20x=13=2^3+2^2+2^0,分为[1,8],[9,12],[13,13][1,8],[9,12],[13,13]三个区间,区间和分别为c[8],c[12],c[13]c[8],c[12],c[13]

3.除树根外,每个内部节点c[x]c[x]的父节点是c[x+lowbit(x)]c[x+lowbit(x)]

4.树的深度为O(logN)O(logN)。因此可以保证在O(logN)O(logN)的时间内查询前缀和

在这里插入图片描述



树状数组应用总结

主要分为三种:

  • 单点修改,单点查询(最基础)
  • 区间修改,单点查询
  • 区间修改,区间查询

应用一:单点修改,单点查询

最基础的定义

查询
int ask(int x)
{
    int ans=0;
    for (;x;x-=x&-x)ans+=c[x];
    return ans;
}
修改
void add(int x,int y)
{
    for (;x<=n;x+=x&-x)c[x]+=y;
}

例题:POJ2182
题意:有nn头奶牛,身高1n1-n不等,站成一列,已知第ii头奶牛前面有AiA_i头比他矮,求出所有身高。
思路:从后往前思考。AnA_n表示前n1n-1头牛有AnA_n头比最后一头矮,由于不存在重复身高,最后一头牛的身高肯定是排第An+1A_n+1位的,同理第ii头的身高一定是排第Ai+1A_i+1位,但是已经出现过的身高就得排除。可以通过维护一个初始全1的01序列,查询第kk个1的位置并且将其修改为0。用二分+树状数组即可
代码

int n,A[maxn],c[maxn],ans[maxn];
int ask(int x){int ans=0;for(;x;x-=x&-x)ans+=c[x];return ans;}
void add(int x,int y){for(;x<=n;x+=x&-x)c[x]+=y;}
int solve(int x)
{
    int l=1,r=n;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if (ask(mid)<x)l=mid+1;
        else r=mid;
    }
    return l;
}
int main()
{
    scanf("%d",&n);
    A[1]=0;rep(i,2,n)scanf("%d",&A[i]);
    rep(i,1,n)add(i,1);
    for (int i=n;i>=1;i--)
    {
        ans[i]=solve(A[i]+1);
        add(ans[i],-1);
    }
    rep(i,1,n)W(ans[i]);
}


应用二:区间修改,单点查询

新建数组bb,前缀和b[1b[1~x]x]反映了指令"C l r d"对a[x]a[x]的影响
1.把b[l]+db[l]+d
2.把b[r+1]db[r+1]-d
观察一下数组bb的变化:
1.当1x<l1\leq x< l时:前缀和b[1b[1~x]x]无变化
2.当lxrl \leq x \leq r时:前缀和b[1b[1~x]x]增加了dd,因为b[l]b[l]增加了dd
3.当r<xnr< x \leq n时:前缀和b[1b[1~x]x],因为一加一减刚好抵消

也就是说 对于区间[l,r][l,r]内的数的影响,都体现在了数组bb的前缀和中
而求前缀和就是树状数组的基础操作,累加上原先的a[x]a[x]即可得到"Q x"的答案

蓝书例题:
代码

int n,m,l,r,x,a[maxn],c[maxn];
char s[5];
int ask(int x){int ans=0;for (;x;x-=x&-x)ans+=c[x];return ans;}
void add(int x,int y){for (;x<=n;x+=x&-x)c[x]+=y;}
int main()
{
    scanf("%d%d",&n,&m);
    rep(i,1,n)scanf("%d",&a[i]);
    while(m--)
    {
        scanf("%s",s);
        if (s[0]=='Q')
        {
            scanf("%d",&x);
            W(ask(x)+a[x]);
        }
        else if (s[0]=='C')
        {
            scanf("%d%d%d",&l,&r,&x);
            add(l,x);
            add(r+1,-x);
        }
    }
}


应用三:区间修改,区间查询

首先仍是维护一个数组bba[x]a[x]变化量就是前缀和b[1b[1~x]x]
那么原数组aa的前缀和a[1a[1~x]x]变化量就是:
i=1xj=1ib[j]=(x+1)i=1xb[i]i=1x(i×b[i])\sum_{i=1}^x\sum_{j=1}^i b[j]=(x+1)\sum_{i=1}^x b[i]-\sum_{i=1}^x (i×b[i])
第一部分就是维护bb的前缀和
第二部分就是维护i×b[i]i×b[i]的前缀和,令i×b[i]=c2[x]i×b[i]=c2[x]
由于只是乘了个ii,故与之前对应的操作是:
1.把c2[l]+ldc2[l]+ld
2.把c2[r+1](r+1)dc2[r+1]-(r+1)d
观察一下数组bb的变化:
lxrl \leq x \leq r时:前缀和c2[1c2[1~x]x]增加了ldld,因为c2[l]c2[l]增加了ldld

综上所述:aa的原始前缀和+aa的前缀和变化量即为最终的aa的前缀和
Sum[r]=sum[r]+(r+1)i=1rb[i]i=1r(i×b[i])Sum[r]=sum[r]+(r+1)\sum_{i=1}^r b[i]-\sum_{i=1}^r (i×b[i])
=sum[r]+(r+1)ask1(r)ask2(r)=sum[r]+(r+1)ask1(r)-ask2(r)

区间查询即输出Sum[r]Sum[l1]Sum[r]-Sum[l-1]

蓝书例题:
代码

int n,m;
ll a[maxn],c1[maxn],c2[maxn],sum[maxn],l,r,x;
char s[5];
ll ask1(ll x){ll ans=0;for(;x;x-=x&-x)ans+=c1[x];return ans;}
ll ask2(ll x){ll ans=0;for(;x;x-=x&-x)ans+=c2[x];return ans;}
void add1(ll x,ll y){for(;x<=n;x+=x&-x)c1[x]+=y;}
void add2(ll x,ll y){for(;x<=n;x+=x&-x)c2[x]+=y;}
int main()
{
    scanf("%d%d",&n,&m);
    rep(i,1,n)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
    while(m--)
    {
        scanf("%s",s);
        scanf("%lld%lld",&l,&r);
        if (s[0]=='Q')
        {
            ll ans1=sum[r]-sum[l-1];
            ll ans2=(r+1)*ask1(r)-l*ask1(l-1);
            ll ans3=-ask2(r)+ask2(l-1);
            WW(ans1+ans2+ans3);
        }
        else
        {
            scanf("%lld",&x);
            add1(l,x);
            add1(r+1,-x);
            add2(l,l*x);
            add2(r+1,-(r+1)*x);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章