【第21期】观点:人工智能到底用 GPU?还是用 FPGA?

树状数组

原创 2016年08月30日 13:47:59

树状数组作为一种数据结构,在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;  
} 

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

版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

hdu 3436 Queue-jumpers 树状数组

Queue-jumpers Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) To...

初识树状数组

树状数组:是一个查询和修改复杂度都为log(n)的数据结构,假设数组A[1..n],那么查询A[1]+...+A[n]的时,间是log级别的,而且是一个在线的数据结构。 树状数组原理如下:主要是依靠神奇的二进制转换进行保存数据。所以我们需要使用lowbit函数。 [code="java"] const int D = 1000005; int s[D]; inline int lowbit(int x) { return x & (-x); }//用lowbit函数算出该数在二进制下的最后一个1的位置; void add(int x, int w) { while (

树状数组--一维模板

树状数组不是很懂只能给个模板了。 原理是二分法。#include #include #include using namespace std; int tree[100005]; int n; in...

树状数组

<a id="ctl02_TitleUrl" class="postTitle2" href="http://www.cnblogs.co

树状数组简单总结

1、树状数组最早用于数据压缩。 2、c[i]的初始位置是去掉最低位的1之后加上1.(最后一个1代表的权值表示c中拥有a的个数。) 3、c[i]下标的起点是下标 x=i-[i&(-i)]+1; 4、树状...
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)