树状数组(二叉索引树)(uva 1428 - Ping pong )

本文介绍树状数组的基本概念及其在RMQ算法中的应用,包括如何通过树状数组进行快速查询和更新操作,提供了具体的实现代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

     个人认为树状数组在功能上是RMQ算法的升级版。

     树状数组支持以下两种操作:

         Add(x,d)  : 让Ax增加d;

         Query(L,R): 计算A(l)+A(l+1)+A(l+2)+....+A(R);

     首先,我们定义lowbit(x)为x的二进制表达式中最右边的1所对应的数值(不是序列号)。比如5的二进制是101,lowbit(5)=1;6的二进制是110,lowbit(6)=2。

     如何计算lowbit呢?lowbit(x)=x&(-x);因为在计算机中负数是正数按位取反加1得到的。

     

变成一棵树:(叫做树状数组的原因吧<_<)

               

     红色节点是我们要存储的,红色节点存储的是红色节点加上前面蓝色节点的和。

     比如c8代表的是a1-a8的和,c12代表的是a9-a12的和。(c为辅助数组,a为存储具体数值的数组)

     并且每层的lowbit的值是相同的。(觉得网络上的图意思表达的不明显,自己不会用数学绘图软件T-T,就用excel自己做了个,不太好看见谅。)

              ci=a(i-lowbit(i)+1)+a(i-lowbit(i)+2)+...a(i);

     有了数组c后如何计算前缀和Si呢?顺着节点i往左走,边走边“爬”(注意并不一定沿着树爬),把沿途的ci加起来就行了,如下图计算s11:

  

     修改一个ai,需要更新哪些ci呢?从ci向右走,边走边“爬”,如下图修改a3:

  

 

我的模板:

  

int lowbit(int x){
  return x&(-x);
}
int sum(int x){
  int ret=0;
  while (x>0) {
    ret+=C[x];x-=lowbit(x);
  }
  return ret;
}
int add(int x,int d){
  while (x<=n){
    C[x]+=d;x+=lowbit(x);
  }
  return 0;
}
int query(int L,int R){
  return sum(R)-sum(L-1);
}

练习题可以做uva 1428 - Ping pong 

   考虑第i个人当裁判的情况。加上a1到a(i-1)中有ci个比ai小,那么有(i-1)-ci个比ai大;同理,假设a(i+1)到a(n)有di个比ai小,那么就有(n-i)-di个比ai大。此时就有ci(n-i-di)+(i-ci-1)di种比赛。问题就变成求ci和di了。

  ok,ci可以这样求,用辅助数组X[j]表示到目前为止,有几个ai与j相等,则ci就是X[1]...X[ai-1]的前缀和了。

  时间复杂度为O(n);

我的代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
const int maxn =20000;
using namespace std;
int C[200009],a[maxn],d[maxn],b[maxn],n,maxd;
int lowbit(int x){
  return x&(-x);
}
int sum(int x){
  int ret=0;
  while (x>0) {
    ret+=C[x];x-=lowbit(x);
  }
  return ret;
}
int add(int x,int d){
  //printf("%d\n",maxd);
  while (x<=maxd){
    C[x]+=d;x+=lowbit(x);
  }
  return 0;
}


int main (){
  int T;scanf("%d",&T);
  while (T--){
    memset(C,0,sizeof(C));
    scanf("%d",&n);maxd=0;
    for (int i=0;i<n;i++){
      scanf("%d",&a[i]);
      maxd=max(maxd,a[i]);
    }
    for (int i=0;i<n;i++){
      d[i]=sum(a[i]-1);
      add(a[i],1);
    }
    memset(C,0,sizeof(C));
    for (int i=n-1;i>=0;i--){
      b[i]=sum(a[i]-1);
      add(a[i],1);
    }
    long long ans=0;
    for (int i=0;i<n;i++){
      ans+=d[i]*(n-i-1-b[i])+(i-d[i])*b[i];
    }
    printf("%lld\n",ans);
  }
  return 0;
}


如果每个人都能力值不唯一的话(>_>我没有看错题),可以用下面的代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
const int maxn =20000;
using namespace std;
int C[200009],C1[200009],a[maxn],d[maxn],b[maxn],d1[maxn],b1[maxn],n,maxd;
int lowbit(int x){
  return x&(-x);
}
int sum(int x){
  int ret=0;
  while (x>0) {
    ret+=C[x];x-=lowbit(x);
  }
  return ret;
}
int add(int x,int d){
  while (x<=maxd){
    C[x]+=d;x+=lowbit(x);
  }
  return 0;
}
int sum1(int x){
  int ret=0;
  while (x<maxd+1) {
    ret+=C1[x];x+=lowbit(x);
  }
  return ret;
}
int add1(int x,int d){
  while (x>=1){
    C1[x]+=d;x-=lowbit(x);
  }
  return 0;
}
int main (){
  int T;scanf("%d",&T);
  while (T--){
    memset(C,0,sizeof(C));
    memset(C1,0,sizeof(C1));
    scanf("%d",&n);maxd=0;
    for (int i=0;i<n;i++){
      scanf("%d",&a[i]);
      maxd=max(maxd,a[i]);
    }
    for (int i=0;i<n;i++){
      add(a[i],1);
      add1(a[i],1);
      d[i]=sum(a[i]-1);
      d1[i]=sum1(a[i]+1); 
    }
    memset(C,0,sizeof(C));
    memset(C1,0,sizeof(C1));
    for (int i=n-1;i>=0;i--){
      add(a[i],1);
      add1(a[i],1);
      b[i]=sum(a[i]-1);
      b1[i]=sum1(a[i]+1);
    }
    long long  ans=0;
    for (int i=0;i<n;i++){
      //printf("%d\n",i+1);
      //printf("左小%d 右小%d\n",d[i],b[i]);
      //printf("左大%d 右大%d\n",d1[i],b1[i]);
      ans+=d[i]*b1[i]+d1[i]*b[i];
    }
    printf("%lld\n",ans);
  }
  return 0;
}


     

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值