树状数组之求逆序对
逆序对
设 A 为一个有 n 个数字的有序集 (n>1), 其中所有数字各不相同。 如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A [i] > A [j], 则 <A [i], A [j]> 这个有序对称为 A 的一个逆 序对,也称作逆序数。 例如,数组 (3,1,4,5,2) 的逆序对有 (3,1),(3,2),(4,2),(5,2), 共 4 个。
原理
我们有以下的设定
- a[]={0,3,1,4,5,2} 其中 3,1,4,5,2 是我们求逆序对序列
- c[6]={0} 是一个树状数组,初始为空
- A[i] 表示大小为 i 的数出现了几次
如图:
核心: 在我操作第 i 个数的时候,已经有 k 个数比第 i 个数大,那这个时刻我们知道有 k 个逆序对.怎么求 k?
k=i−query(a[i])
如果我们进行下面的操作:
for(i=1;i<=5;i++){
update(a[i],1);
}
- i=1 的时候,c[3]=1,c[4]=1, 那么 query(3)=1,query(4)=1
- query(3)=1 表示 1->3 范围内的数字有一个
- query(3)=1 也可以表示,前 1 个数中,<=3 的数有一个
- 1-query(3)=0 表示:前 1 个数中,>3 的数有 0 个
- 同理 i=2, 时候 update(a[2],1)
- query(a[2]) 表示前 2 个数中,<=a[2] 的数有 query(a[2]) 个
- 2-query(a[2]) 表示前 2 个数中,>a[2] 的数有 2-query(a[2]) 个
核心思想:在处理到第 i 个数的时候,i−query(a[i]) 表示有前 i 个数之中有几个数比第 i 个数大,将所有结果加起来就是逆序对的数量
如图:
核心操作
#include <iostream>
#include <string.h>'
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define Ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0);
#define ll long long
const int N = 500056;
int c[N],ans[N];
int n;
int lowbit(int x) { return (x & -x); }
int getsum(int n){
int sum = 0;
while(n>0){
sum += c[n};
n -= lowbit(n);
}
return sum;
}
void change(int x,int num){
while(x>=n}{
c[x] += num;
x += lowbit(x);
}
}
int main(){
scanf("%d",&n);
ll cnt = 0;
for(int i=1; i<=n; i++){
int t;
scanf("%d",&t);
chang(t,1): // 求逆序对就要初始化c数组为1
cnt += (ll)i - getsum(t); //核心思想
}
printf("%d",cnt);
return 0;
}
例题
luogu P1966 [NOIP2013 提高组] 火柴排队
思路:
-
定义一个结构体,来存储每排火柴的高度和每排所呆的位置
-
由于高度差是相对的,所以可以用第一排或者第二排做参照找出其有多少个大小不一的即逆序对即可
-
其中相对的参照算法:
- 先对两排的火柴按高度进行排序(结构体排序)
- 开一个数组S,记录排完序后两排火柴之间的相对位置,这里我们S的下标为第一排排完序的下标,其值为对应的第二排排完序的下标即:
for (int i = 1; i <= n; i++) s[a[i].pos] = b[i].pos;
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
const int N = 600005;
const int mod = 99999997;
int n;
long long c[N],s[N]; //c[n]表示a[1~n]的和,a数组省略,s[N]是参照对比后的数组
struct node {
int val, pos;
}a[N], b[N];
int lowbit(int x) {
return (x & -x);
}
ll getsum(int n) {
ll sum = 0;
while (n > 0) {
sum = (sum + c[n]) % 99999997; //以防传入时的数据过大,这里提前mod
n -= lowbit(n);
}
return sum;
}
void change(int x) {
while (x <= n) {
c[x]++;
x += lowbit(x);
}
}
bool cmp(node a, node b) { //结构体排序,注意包含相同数时的情况,否则会报段错误
if (a.val != b.val)
return a.val > b.val;
else return a.pos > b.pos;
}
int main() {
cin >> n;
mem(c, 0);
mem(s, 0);
node a[N];
node b[N];
//数据传入
for (int i = 1; i <= n; i++) {
cin >> a[i].val;
a[i].pos = i;
}
for (int i = 1; i <= n; i++) {
cin >> b[i].val;
b[i].pos = i;
}
//排序
sort(a + 1, a + n + 1, cmp);
sort(b + 1, b + n + 1, cmp);
//进行参照对比,得到以身高为参照的位置信息
for (int i = 1; i <= n; i++)
s[a[i].pos] = b[i].pos;
ll cnt = 0;
for (int i = 1; i <= n; i++) {
change(s[i]);
cnt += i-getsum(s[i]);
}
cout << cnt % 99999997 << endl;
return 0;
}