之前已经讲道树状数组的几个好用的方法(链接:https://blog.csdn.net/REfusing/article/details/82350189),这里就不过多讲述了,这篇这要讲述树状数组用在逆序对的个数。
1.什么是逆序对?
- 逆序对就是设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。例:在5 4 6 7 2中逆序对有<5,4>, <5,2>, <4,2>, <6,2>, <7,2>总共5个。
2.逆序对怎么求?
- 5 4 6 7 2 这几个的逆序数可以这么求,假设这个数组中没有任何一个数,不断的向里面插入数,然后统计比最新插入的这个数大的个数m(i),然后把所有的m(i)加一起。所得的记过就是逆序对的对的个数。
- 首先插入5, 5 前面没有比5 大的,所以sum = 0。
- 然后插入4, 4前面有一个比他大的数, sum+1,sum此时等于1。
- 然后在插入6, 6前面没有比6大的数,所以sum不用更新。
- 再插入7, 7 前面同样没有比7大的数,sum也不用更新。
- 再插入2, 2前面有4个比2大的数,sum + 4, 此时sum = 5.
- 5就是逆序对数。
3.用树状数组的作用
- 用树状数组求逆序对有点像桶排序,就比如5 4 6 7 2,初始化数组为0,然后插入一个5,a[5]++(为什么是a[5]++,因为可能5有重复的,所以是加加,而不是直接赋值为1,然后累加这个数前面的所有数,然后在用此时的位置减去累计的结果,就是此时插入位置的逆序数。数状数组就是用来查找的,树状数组有个功能单点修改区间查询的功能,所以可以每add(pos, 1),此处的位置加1,然后查询前面有多少个比大他的数。在这里树状数组的作用就是减少查询的时间复杂度,如果数据小的话直接就利用像桶排的方法,如果数据比特别大的话可以利用树状数组也不行,因为数组不能开这么大,所以就有了一个比较好用的方法
4.如果数据较大怎么办?
- 如果数据较大那么就利用离散的方法处理数据,其他的和之前没有啥区别,如何用离散的方法处理数据呢?就是把这些数缩小,给的原始数据就是用来判断大小的,所以可以把这些数据按照从小到大的顺序排序,如果这个数这第m小,让m取代原来原来的数,让m放置在原来的位置。
- 例5 4 6 7 2 ,5是第3小,所以5还是被3取代, 4是第2小所以4被2取代,6是第4小的,所以6被4取代,7是第5小的数,所以7倍5取代,2是第1小的数,所以2被1取代,最后变成 3 2 4 5 1.其实跟之前大小是一样的只是数据的规模变小了。
以CodeVs《1688 求逆序对》为例
链接:http://codevs.cn/problem/1688/
此题的数据比较小最多才1e5所以可以不用离散化处理直接就可以进行处理。
#include<stdio.h>
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
#define N 100001
int c[N] = {0}, a[N], maxn = 0;
int n;
int lowbit(int a){
return a & (-a);
}
void add(int pos, long long val){
for(int i = pos; i <= maxn; i += lowbit(i)){
c[i] += val;
}
}
long long Getsum(int pos){
int sum = 0;
for(int i = pos; i > 0; i -= lowbit(i)){
sum += c[i];
}
return sum;
}
int main(){
cin>>n;
int i;
for(i = 1; i <= n; i++){
cin>>a[i];
if(a[i] > maxn)maxn = a[i];//maxn用来更新最多数组能用到多大,不断更新
}
long long sum = 0;
for(i = 1; i <= n; i++){
add(a[i], 1);
sum += i - Getsum(a[i]);//Getsum用来求插入时,前面有多少比他小的数m,然后总插入的数减去m就是此时你逆序数
}
cout<<sum<<endl;
return 0;
}
下面也是求逆序对,但是这道题的数据就比较大1e9,数组开不来这么大,所以需要进行离散化处理。
洛谷 P1908 逆序对 链接:https://www.luogu.org/problemnew/show/P1908
这道题怎么离散化处理,就是利用结构体加上排序
struct Node{
long long val;
int id;
}node[N];
bool cmp(Node A, Node B){
return A.val < B.val;
}
sort(node + 1, node + n + 1, cmp);
为什么这么操作,因为这么做就是相当于val和id就是一个映射关系,id就代表与原始数据意义对一,然后遍历一遍,找到每个数是第几小的。下面是对这些数据进行缩小范围的过程。
int t = 1;
b[node[1].id] = t;
long long k = node[1].val;
for(i = 2; i <= n; i++){
if(node[i].val != k){
k = node[i].val;
t++;
}
b[node[i].id] = t;
}
为什么这么做,因为这么做就把数据大小一样的设置为同样是第 i 小。
#include<stdio.h>
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
#define N 500001
long long c[N] = {0};
int b[N] = {0};
int n;
struct Node{
long long val;
int id;
}node[N];
bool cmp(Node A, Node B){
return A.val < B.val;
}
int lowbit(int a){
return a & (-a);
}
void add(int pos, long long val){
for(int i = pos; i <= n; i += lowbit(i)){
c[i] += val;
}
}
long long Getsum(int pos){
long long sum = 0;
for(int i = pos; i > 0; i -= lowbit(i)){
sum += c[i];
}
return sum;
}
int main(){
scanf("%d", &n);
int i;
long long a;
for(i = 1; i <= n; i++){
scanf("%lld", &a);
node[i].val = a;
node[i].id = i;
}
long long sum = 0;
sort(node + 1, node + n + 1, cmp);
int t = 1;
b[node[1].id] = t;
long long k = node[1].val;
for(i = 2; i <= n; i++){
if(node[i].val != k){
k = node[i].val;
t++;
}
b[node[i].id] = t;
}
for(i = 1; i <= n; i++){
add(b[i], 1);
sum += i - Getsum(b[i]);
}
printf("%lld\n", sum);
return 0;
}