什么是逆序对
给一个序列:3 2 1 5 4 ,其中(3,2),(3,1),(2,1),(5,4)就叫做逆序对,下面将介绍两种求逆序对的方法,一种是利用树状数组,也叫线段树的方法来求逆序对的数目,另一种是利归并排序的思想来就逆序对的数目,通过逆序对的数目我们可以知道一个数组利用冒泡排序的思想可以在几步之内完成.
利用树状数组求逆序对的个数
什么是树状数组
关于树状数组的介绍可以参考博客小白都能看懂的树状数组解析
通过这个我们应该了解到一个树状数组的两个核心的东西就是查询和单点更新,利用这两个东西我们就可以计算出一个数组中的逆序对的数目,查询就是计算前n项的和,单点更新就是更新数组中的某一个元素的值,并更新树状数组中的相关量的值
下面我来介绍以下利用树状数组逆序对的主要思想:
首先我们要明白求逆序对的主要思想就是在原序列中,看每一个数的前面有几个数比他大,再以上诉序列举例:
a = [3,2,1,5,4]
- 3 的前面没有比他小的数
- 2 的前面有一个3比他大(3,2)
- 1 的前面2 和 3 都比他大(3,1)(2,1)
- 5 的前面没有数比他大
- 4 的前面有5比他大(5,4)
所以当数列按有小到大的顺序排列好之后,我们依次按排列好之后的数据顺序更新树状数组.然后再来查询树状数组前n项的和,与之前的前n项和相减就可以得出前面有几个数比他大,值得注意的是,由于更新前后的树状数组的值值发生了+1部分的变化 ,所以可以将树状数组的值全部置为0,(反正最后都是要减去的,不如来的简单一点,下面贴上AC代码)
找了个更好理解的方式
原序列 3 2 1 5 4
排序之后 1 2 3 4 5
index_primary 3 2 1 5 4
-
先求出原序列中的前n项和
sum[1] = 3
sum[2] = 5
sum[3] = 6
sum[4] = 11
sum[5] = 15 -
然后按照从小到大的顺序更新:先更新位于原数据位置第三的元素1,更新后sum[3] = 7说明1的后面没有数比1小
-
然后更新位于位置2的数据,更新后sum[2] = 6, 2 - (6 - 5) = 1,说明2的后面有一个数比2小,按照同样的思路可以更新下面的几个数据
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
struct N{
int value;
int index; //索引
};
const int MaxN = 1e6 + 5;
N node[MaxN];
int bitree[MaxN] = {0};
int n;
bool cmp(N n1,N n2){
if(n1.value != n2.value){
return n1.value < n2.value;
}
else{
return n1.index < n2.index; //如果大小相等,下标来代表数据的大小
}
}
inline int lowbit(int n){ //这种inline函数一般用于代码较短且使用频率高的函数
return n & (- n);
}
void Add(int x){ //单点更新
while(x <= n){ //注意这里是小于等于
bitree[x] += 1;
x += lowbit(x);
}
}
int Query(int x){ //查询
int sum = 0;
while(x > 0){
sum += bitree[x];
x -= lowbit(x);
}
return sum;
}
int main(){
memset(bitree,0,sizeof(bitree));
scanf("%d",&n);
for(int i = 1; i <= n; i ++){
scanf("%d",&node[i].value);
node[i].index = i;
}
sort(node + 1,node + n + 1,cmp); //对结构体中的数据进行排序
ll res = 0; //解
for(int i = 1; i <= n;i ++){
Add(node[i].index); //单点更新
res += (i - Query(node[i].index));
}
//输出
printf("%lld\n",res);
return 0;
}