树状数组求逆序数

来源:互联网 发布:看韩剧的软件 编辑:程序博客网 时间:2024/04/30 01:12

1、基本概念

树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。其结构与线段树很相似,只是用树状数组能够解决的问题,线段树基本上都能解决,而线段树能解决的树状数组不一定能解决。此外,树状数组效率要高很多。线段树结构示意如下:
这里写图片描述

令该树的结点编号为C1,C2…Cn。令每个结点的值为这棵树的值的总和,则有:

C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16

设树的节点编号为x,那么这个节点所含区间元素个数为2^k(其中k为x二进制末尾0的个数)。由于区间最后一个元素必然为Ax,则有

Cn = A(n – 2^k + 1) + … + An

基本操作

预备函数

定义一个Lowbit函数,返回参数转为二进制后,最后一个1的位置所代表的数值.

例如,Lowbit(34)的返回值将是2;而Lowbit(12)返回4;Lowbit(8)返回8。将34转为二进制,为0010 0010,这里的”最后一个1”指的是从2^0位往前数,见到的第一个1,也就是2^1位上的1.

程序上,((Not I)+1) And I表明了最后一位1的值,程序如下

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

新建

定义一个数组 BIT,用以维护A的前N项和,则:

BITi=j=ilowbit(i)+1iAj

即有

void build( ){    for (int i=1;i<=MAX_N;i++)    {        BIT[i]=A[i];        for (int j=i-1; j>i-lowbit(i); j--)            BIT[i]+=A[j];    }}

修改

假设现在要将A[i]的值增加delta,那么,需要将BIT[i]覆盖的区间包含A[i]的值都加上K.这个过程可以写成递归,或者普通的循环.需要计算的次数与数据规模N的二进制位数有关,即这部分的时间复杂度是O(LogN).

void edit(int i, int delta){    for (int j = i; j <= MAX_N; j += lowbit(j))        BIT[j] += delta;}

求和

假设我们需要计算

i=1kAi
的值.

首先,将ans初始化为0,将i计为k。将ans的值加上BIT[P];将i的值减去lowbit(i);重复步骤2~3,直到i的值变为0。

int sum (int k){    int ans = 0;    for (int i = k; i > 0; i -= lowbit(i))        ans += BIT[i];    return ans;}

总结

初始化复杂度最优为O(Nlog N);单次询问复杂度O(log N),其中N为数组大小;单次修改复杂度O(log N),其中N为数组大小;空间复杂度O(N)。


2、树状数组求逆序数

逆序数

一个排列中,对于顺序排列的两个数,若前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。逆序数可如下计算:标出每个数右面比它小的数的个数,它们的和就是逆序数。

例如求53421的逆序数
t(53421)=4+2+2+1+0=9

按上述方法求逆序数,我们开一个数组c[maxn]并初始化为0,来记录前面数据的出现情况;当数据a出现时,就令c[a]=1。那么某个数a的逆序数,只需要算出在当前状态下c[a+1,maxn]中有多少个1即可。这样求一个排列的逆序数时间复杂度为O(n^2)。

树状数组求逆序数

算法思路:把排列中的数依次插入到树状数组, 每插入一个数a, 统计比他小的数的个数,对应的逆序为 i- get_sum(a),其中 i 为当前已经插入的数的个数, get_sum(a)为小于或等于a的数的个数,i- get_sum(a) 即比a大的个数, 即逆序的个数。最后把所有逆序数求和,即得整个排列的逆序数。

例如:求53421的逆序数
插入数据a=5,逆序数为1-1=0;插入a=3,逆序数为2-1=1;插入a=4,逆序数为3-2=1;插入a=2,逆序数为4-1=3;插入a=1时,逆序数为5-1=4。整个排列的逆序数 t(53421)=0+1+1+3+4=9。

按上述方式求逆序数,我们开一个数组d[maxn]并初始化为0,d[k]记录下k结点所管辖范围内当前状态有多少个数,即该范围内小于或等于k的数出现的次数;当添加数据a时,就向上更新d,那么对于a的逆序数时,只需要算i-getsum(a);i表示a在排列中的序号,sum(a)表示第i个位置之前出现了多少个1.

程序如下:

#include <iostream>#include <cstring>#define MAXN 100001#define Low_bit(x) (x&(-x))using namespace std;int d[MAXN],n;void update(int a){    while(a<=MAXN)   //之所以不是a<=n,是为了程序对整数域中非连续的排列依然适用(如:18 14 16 5 1)    {        d[a]++;        a+=Low_bit(a);    }}int get_sum(int a){    int sum=0;    while(a)    {        sum+=d[a];        a-=Low_bit(a);    }    return sum;}int main(){    int a,ans;    cin>>n;    memset(d,0,sizeof(d));    ans=0;    for(int i=1;i<=n;i++)    {       cin>>a;       update(a);       ans+=i-get_sum(a);    }    cout<<ans<<endl;    return 0;}

算法改进:上述算法的关键在于,读入数据的最大值为maxn,由于maxn较小,所以可以用数组来记录状态。当maxn较大时,直接开数组显然是不行了,一种解决办法就是离散化。由于求逆序时只须要求数据的相应大小关系不变,离散化就是建立一个一对一的映射。

例如:求500 300 400 20 10的逆序数
上述排列的逆序数与排列5 3 4 2 1的逆序数是相同的,离散化之后求解将比直接求解更加高效。

离散化方法:定义一个结构体

struct Node{             int data;            int pos;     // 对应数据的输入顺序 };

先对 data 升序排序, 排序后,pos 值对应于排序前 data 在数组中的位置。再定义一个数组 p[N], 这个数组为原数组的映射。以下语句将按大小关系把原数组与 1到 N 建立一一映射。

int id= 1;p[d[1].pos]= 1;for( int i= 2; i<= n; ++i )    if( d[i].data== d[i-1].data )         p[ d[i].pos ]= id;    else        p[ d[i].pos ]= ++id;

改进后程序如下:

#include <iostream>#include <cstring>#include <algorithm>#define MAXN 100#define Low_bit(x) (x&(-x))using namespace std;struct Node{    int data;    int pos;};bool compare(Node no1, Node no2){    return no1.data<no2.data;}int d[MAXN],p[MAXN],n;Node start[MAXN];void update(int x){    while(x<=n)    //排列在整数域连续化(不考虑次序)后便可用x<=n    {        d[x]++;        x+=Low_bit(x);    }}int get_sum(int x){    int sum=0;    while(x)    {        sum+=d[x];        x-=Low_bit(x);    }    return sum;}int main(){    int ans;    cin>>n;    memset(d,0,sizeof(d));    ans=0;    for(int i=1;i<=n;i++)    {        cin>>start[i].data;        start[i].pos=i;    }    sort(start+1,start+n+1,compare);    int id=1;    p[start[1].pos]=1;    for(int i=2; i<=n; ++i)        if(start[i].data==start[i-1].data)            p[start[i].pos]=id;        else            p[start[i].pos]=++id;    for(int i=1;i<=n;i++)    {       update(p[i]);       ans+=i-get_sum(p[i]);    }    cout<<ans<<endl;    return 0;}

值得注意的一点是,当有数据0出现时,由于0&0=0,无法更新,此时我们可以采取加一个数的方法将所有的数据都变成大于0的。

0 0
原创粉丝点击