百度百科:树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
树状数组可以解决线段树能够解决的问题,且更加节省空间。
线段树的讲解(可以看这位大佬的博客):https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html
树状数组的讲解(另一位大佬的博客):https://blog.csdn.net/bestsort/article/details/80796531
这里主要写一下利用树状数组来求解逆序对的个数。
逆序对(百度百科): 如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
简单来说就是一段序列里面每个数所在位置之前的位置上比这个数大的数字个数之和。
例如 1 2 3 5 4
只有 4 前面的 有一个数字 5 比其大 故构成了一个逆序对。
而如何求一个序列中所有逆序对的个数呢?
树状数组可以很好的解决这个问题。首先我们知道树状数组可以利用数组将我们所需要的数字给存储起来,并且查询也很方便。
那么我们可以这样操作: 假如序列是: 999 3 56 888
那么 我们可以首先 给他们编号 如 999--1 3--2 56--3 888--4
那么 排序(升序)后 可以看到 序列成为了 2 3 4 1
则 C[2]=1 C[3]= 2 C[4]=3 C[1]=4
C[i] = A[i-2^k+1]+.....A[i];
(其实也就是将A[i] =1 的过程 -- 也就是将A[i]加入序列 -- A[i]=1 表示加入 =0 表示未加入)
然后我们每次找到 小于 i 的个数, 然后 i 减去这个 数字就是这个当前数字的逆序对数 即 sum += getsum(i);
getsum(i) 求的是 小于 i 的之和 其实就是 小于当前数字的个数 i-getsum(i) 便是 大于当前数字的个数--即逆序对。
补充:编号就可以不需要依赖当前的值,而只需要关注其编号, 其编号根据其键值排序后的序列,依次按照当前序列的ID加入树状数组,就可以求出逆序对数。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node {
int val;
int id;
} a[100000+10];
int b[100000+10];
int cmp(node a,node b) {
if(a.val==b.val)
return a.id<b.id;
return a.val<b.val;
}
int lowbit(int x) {
return (x & (-x));
}
void update(int id,int v,int n) {
int i =id;
while(i<=n) {
b[i]+=v;
i+=lowbit(i);
}
}
int getsum(int i) {
int sum=0;
while(i>0) {
sum+=b[i];
i-=lowbit(i);
}
return sum;
}
int main() {
int t;
scanf("%d",&t);
int n;
while(t--) {
int ans=0;
scanf("%d",&n);
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int i = 1; i<=n; i++) {
scanf("%d",&a[i].val);
a[i].id=i;
}
sort(a+1,a+n+1,cmp);
for(int i = 1; i<=n; i++) {
update(a[i].id,1,n);
ans+=i-getsum(a[i].id);
}
printf("%d\n",ans);
}
return 0;
}