逆序对,简单来说,就是i > j
*,但a[i] < a[j]
,那么a[i] 和 a[j]
就是一组逆序对
求逆序对有三种方式——
- 暴力 复杂度 O(n2) 谁用谁T 不多赘述了
- 归并排序 复杂度 O(nlogn)
- 树状数组 复杂度 O(nlogn)
归并排序
首先看一下归并排序的原理,就是将一个序列无限二分,直到每一部分都只有一个元素,这时每一部分都有序,然后逐次合并相邻部分,让合并后的各个部分有序
举个栗子
现在我们要合并两个部分:1 3 5 7 9 | 2 4 6 8 10
先比较 1、2,发现 1 < 2,所以把 1 先放到合并后的数组里
现在剩下的两部分是:3 5 7 9 | 2 4 6 8 10
现在比较3、2,发现 3 > 2,所以把 2 放到合并后的数组,由于左半部分是有序的,所以 2 小于左半部分剩下的所有数,但 2 又在左半边剩下的所有数后面,所以 2 和这些数都构成逆序对,逆序对的数量就是mid - i
每一次右半部分的第一个数小于左半部分的第一个数时,右半部分的第一个数和左半部分剩下的所有数都构成逆序对,因此在原来的基础上加上mid - i
即可
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int ans;
int a[N], temp[N];
void merge_pai(int l, int r, int mid)
{
int i = l, j = mid, p = l;
while (i < mid && j <= r)
{
if (a[i] < a[j]) temp[p ++ ] = a[i ++ ];
else
{
temp[p ++ ] = a[j ++ ];
ans += mid - i;
}
}
while (i < mid) temp[p ++ ] = a[i ++ ];
while (j <= r) temp[p ++ ] = a[j ++ ];
p = l;
while (p < mid) a[p ++ ] = temp[p ++ ];
}
void merge_sort(int l, int r)
{
if (l < r)
{
int mid = l + r >> 1;
merge_sort(l, mid);
merge_sort(mid + 1, r);
merge_pai(l, r, mid + 1);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n; cin >> n;
for (int i = 0; i < n; i ++ ) cin >> a[i];
merge_sort(1, n);
cout << ans;
}
树状数组
树状数组的原理在这里就不多说啦,如果有不懂的同学可以去看看这篇
建立一个数组c[i]
,表示元素 i 出现的次数,起初每一项都为0
同时我们也可以得到c[i]
的树状数组tree[i]
每次插入一个数 x ,我们都将利用树状数组单点修改的性质更新tree[i]
的值,此时区间查询c[i - 1]
的前缀和,我们就可以得到在 x 前输出,比 x 的值小的元素个数,用总共输入的元素个数和它相减,就可以得到在 x 前输出,且比 x 大的元素个数,这也就是逆序对的个数了
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int tree[N];
int lowbit(int x)
{
return x & -x;
}
void add(int i, int x)
{
while (i <= N)
{
tree[i] += x;
i += lowbit(i);
}
}
int prefix_sum(int i)
{
int presum = 0;
while (i > 0)
{
presum += tree[i];
i -= lowbit(i);
}
return presum;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n; cin >> n;
int a[n + 1];
int ans = 0;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
add(a[i], 1);
ans += (i - prefix_sum(a[i]));
}
cout << ans;
}