树状数组

树状数组作为一种数据结构,在OI竞赛中也是一项常用常考点,博主为使自己不忘记此数据结构,来写篇小博文

    Above all,树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为 log2n 的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在 log2n 的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。

树状数组


树状数组的核心代码

关于树状数组有一段核心代码,即lowbit函数

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

  eg.1+lowbit(1)=2;
     2+lowbit(2)=4;
     //详见上图

此函数用来找当前树状数组的上一个或下一个数组位置,具体原理博主表示也不清楚,背过就好。


树状数组的经典操作

修改操作
void add(int x,int sum)//给a[x]加上个sum
{
    while(x<=n)
    {
        c[x]+=sum;//给树状数组加上一个值
        x+=lowbit(x);//找下一个数组的位置
    };
};
查询操作
int read(int x)//查询(1,x)内的区间和
{
    int ret=0;
    while(x>=1)
    {
        ret+=c[x];
        x-=lowbit(x);
    };
    return ret;
};

还有一个看似比较高端的操作

int read(int l,int r)//查询(l,r)内的区间和
{
    int ret=0;
    while(r>=l)
    {
        if(r-lowbit(r)<l)
        {
            ret+=a[r];
            r-=1;
        }else
        {
            ret+=c[r];
            r-=lowbit(r);
        };
    }
    return ret;
}

树状数组的实际应用

关于区间求和的程序
#include <cstdio>
#include <cstring>
#define nmax 10
using namespace std;
int a[nmax],c[nmax];
int n;
int lowbit(int x)
{ return x&-x;
}
void modify(int k,int num) //将第k个数修改成num 
{  
    while(k<=n)  
    {  
        c[k]+=num;  
        k+=lowbit(k) ;
    }  
} 
int add(int k)//1~k的区间和  
{  int sum=0;  
    while(k>0)  
    {  
        sum+=c[k];  
        k-=lowbit(k);  
    }  
    return sum;  
}  
int main()
{ int m;
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++)
  {scanf("%d",&a[i]);
   modify(i,a[i]);
  }
  int t,x,y;
  for(int i=1;i<=m;i++)
  { scanf("%d%d%d",&t,&x,&y);
    if(t==0) 
      { modify(x,-a[x]);
        modify(x,y);
        a[x]=y;
      }
     else 
      printf("%d",add(y)-add(x-1));
  }
  return 0;
}

关于逆序对

求逆序的思路: 可以把数一个个插入到树状数组中, 每插入一个数, 统计比他小的数的个数getsum( data[i] ),对应的逆序对个数为 i- getsum( data[i] ),其中 i 为当前已经插入的数的个数, getsum( data[i] )为比 data[i] 小的数的个数,i- getsum( data[i] ) 即比 data[i] 大的个数, 即逆序的个数。最后需要把所有逆序对个数求和,就是在插入的过程中边插入边求和。

但如果数据比较大,就必须采用离散化方法, 假设输入的数组是9 1 0 5 4, 离散后的结果aa[] = {5,2,1,4,3};
在离散结果中间结果的基础上,那么其计算逆序数的过程是这么一个过程:

  • 输入5, 调用upDate(5, 1),把第5位设置为1

1 2 3 4 5
0 0 0 0 1

计算1-5上比5小的数字存在么? 这里用到了树状数组的getSum(5) = 1操作,现在用输入的下标1 - getSum(5) = 0 就可以得到对于5的逆序数为0。

  • 输入2, 调用upDate(2, 1),把第2位设置为1

1 2 3 4 5
0 1 0 0 1

计算1-2上比2小的数字存在么? 这里用到了树状数组的getSum(2) = 1操作,现在用输入的下标2 - getSum(2) = 1 就可以得到对于2的逆序数为1。

  • 输入1, 调用upDate(1, 1),把第1位设置为1

1 2 3 4 5
1 1 0 0 1

计算1-1上比1小的数字存在么? 这里用到了树状数组的getSum(1) = 1操作,现在用输入的下标 3 - getSum(1) = 2 就可以得到对于1的逆序数为2。

  • 输入4, 调用upDate(4, 1),把第4位设置为1

1 2 3 4 5
1 1 0 1 1

计算1-4上比4小的数字存在么? 这里用到了树状数组的getSum(4) = 3操作,现在用输入的下标4 - getSum(4) = 1 就可以得到对于4的逆序数为1。

  • 输入3, 调用upDate(3, 1),把第3位设置为1

1 2 3 4 5
1 1 1 1 1

计算1-3上比3小的数字存在么? 这里用到了树状数组的getSum(3) = 3操作,现在用输入的下标5 - getSum(3) = 2 就可以得到对于3的逆序数为2。

  • 0+1+2+1+2 = 6 这就是最后的逆序数

离散化的方式:

struct Node
{
    int val;
    int pos;
};
Node node[500005];
int reflect[500005];

val存放原数组的元素,pos存放原始位置,即node[i].pos = i。
把这些结构体按照val的大小排序。
reflect数组存放离散化后的值,即reflect[node[i].pos] = i。
这样从头到尾读入reflect数组中的元素,即可以保持原来的大小关系,又可以节省大部分空间。

  • 下面来看具体程序
#include <iostream>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
using namespace std;    
const int N = 500005;    
struct Node  
{  
    int val;  
    int pos;  
};  

Node node[N];  
int c[N], reflect[N], n;   
bool cmp(const Node& a, const Node& b)  
{  
    return a.val < b.val;  
}    
int lowbit(int x)  
{  
    return x & (-x);  
}    
void update(int x)  
{  
    while (x <= n)  
    {  
        c[x] += 1;  
        x += lowbit(x);  
    }  
}    
int getsum(int x)  
{  
    int sum = 0;  
    while (x > 0)  
    {  
        sum += c[x];  
        x -= lowbit(x);  
    }  
    return sum;  
}    
int main()  
{  
    while (scanf("%d", &n) != EOF && n)  
    {  
        for (int i = 1; i <= n; ++i)   
        {  
            scanf("%d", &node[i].val);  
            node[i].pos = i;  
        }  
        sort(node + 1, node + n + 1, cmp);//排序  
        for (int i = 1; i <= n; ++i) reflect[node[i].pos] = i;//离散化  
        for (int i = 1; i <= n; ++i) c[i] = 0;//初始化树状数组  
        long long ans = 0;  
        for (int i = 1; i <= n; ++i)  
        {  
            update(reflect[i]);  
            ans += i - getsum(reflect[i]);  
        }  
        printf("%lld\n", ans);  
    }   
    return 0;  
} 

最后,希望大家能够看懂
感谢惠读

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值