树状数组求逆序数
来源:互联网 发布:看韩剧的软件 编辑:程序博客网 时间: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项和,则:
即有
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;}
求和
假设我们需要计算
首先,将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的。
- 树状数组求逆序数
- 树状数组求逆序数
- 树状数组 求逆序数
- 树状数组求逆序数
- 树状数组求逆序数
- 树状数组求逆序数
- 树状数组求逆序数
- 树状数组求逆序数
- 树状数组求逆序数
- 树状数组求逆序数
- 树状数组 求逆序数
- 树状数组求逆序数
- 树状数组求逆序数
- 求逆序数(树状数组)
- 树状数组 求逆序数
- 树状数组 求逆序数
- 树状数组求逆序数
- 树状数组求逆序数
- c++模板特化和偏特化
- 摄影摄像基础知识
- NodeJs的express模块4.X
- java读取指定package下的所有class
- 错误输出函数perror和strerror用法
- 树状数组求逆序数
- Linux常见内置命令
- 音频基础知识及编码原理
- Log4j配置详解
- CPU的功能
- 关于内网穿透,内网映射的一些想法
- PHP函数篇详解十进制、二进制、八进制和十六进制转换函数说明
- ubuntu下开启FTP
- nginx多进程锁的实现