题目描述
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a_i>a_j且 i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
输入格式
第一行,一个数 n,表示序列中有 n个数。
第二行 n 个数,表示给定的序列。序列中每个数字不超过 10^9。
输出格式
输出序列中逆序对的数目。
输入输出样例
输入
6 5 4 2 6 3 1
输出
11
说明/提示
对于 25% 的数据,n≤2500
对于 50% 的数据,n≤4×104。
对于所有数据,n≤5×105
Solution#1 归并排序MergeSort
对于归并排序的原理解释指路归并排序https://www.iamshuaidi.com/549.htmlhttps://www.iamshuaidi.com/549.html
为什么归并排序可以用来求逆序对?
- 归并排序利用分治思想,先把原始数组不断“分”,分成只有一个元素的“有序”序列,再把相邻两个数组不断合并也就是“治”,再对任意两个有序序列合并时,在判断大小时只要多加一行代码判断此时指标 j 指向的右边数组里的元素是不是小于指标 i 指向左边数组里的元素,如果是那么 i 到左边数组的最后一个都会比 j 指向的元素大,i~最后一个元素之间的所有元素都和j是逆序对,那么就把这之间的个数加起来就是最终答案
- 归并排序是稳定的,相等的元素位置不会改变
ACCODE
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 500000+5
int a[maxn], tmp[maxn];
ll cnt;
void merge(int left, int center, int right)
{
int i = left, j = center + 1;
for (int k = left; k <= right; k++)
{
if (i > center)
tmp[k] = a[j++];
else if (j > right)
tmp[k] = a[i++];
else if (a[i] <= a[j])
tmp[k] = a[i++];
else
{
/* Hi!添加的代码在这里!*/
cnt += (center + 1 - i);
tmp[k] = a[j++];
}
}
for (int k = left; k <= right; k++)
a[k] = tmp[k];
}
void mergesort(int left, int right)
{
if (left < right)
{
//center指向左边序列的最后一个的下标
int center = (left + right) / 2;
mergesort(left, center);
mergesort(center + 1, right);
merge(left, center, right);
}
}
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
mergesort(0, n - 1);
cout << cnt;
return 0;
}
Solution#2 树状数组(Binary Indexed Tree)
补充说明:
树状数组是通过二进制和lowbit函数来实现一种访问方式来把数组模拟成一颗树
每个结点放的这个结点和其所有子节点的值的和,用tree数组表示树状数组,对每个结点的下标i+lowbit(i)得到的值都是其父节点(原理请看链接),这样在更新一个结点时,就通过这种方式来找到其所有影响到1父节点一一更新
例如:给结点5加1,那么tree[5]+1,其父节点=(101 + 1)二进制=110 二进制=6 十进制,同理跟新110+10 = 1000= 8结点,1000+1000=10000=16结点.....都加上1,维护区间平衡
在查询求和时,每个结点的小标i-lowbit(i) 都能得到和的另一部分
例如:求前六项和,如下图可以看到sum(6) = t[6] + t[4] ( t[4]包括了t[3]+t[2]+t[1] ) ,而 6 = 0b110,6-lowbit(6) = 0b110 - 0b010 = 0b100 = 4,得到的4刚好就是求和中的另一部分
在求逆序对时的其他操作:
- 用a数组存储原始的元素的同时,用p数组存储元素的下标位置,最后再把p数组按照a数组的大小进行排序,这就是对原始数组的离散化操作,例如3个数字1,10000,2,87777经过离散化后的p数组为{1,3,2,4},其中的10000被修改成立3,能很大程度的节省树状数组空间(不然就要开到87777)
- 从p数组第一个开始遍历,每遍历一个元素,就在t数组中相应的位置+1,例如遍历到3,那么t[3]+=1,t[4]+=1,表示在此时小于等于3和小于等于4的元素又出现了一个,updata后就开始query,如果此时小于等于3的个数不足3个,表示还有比3小的数字在后面,这就是逆序对,每个元素的逆序对个数为cnt = i - query(i),差值就是还有几个在后面,就是逆序对。累加求和就是答案
ACCODE
#include <bits/stdc++.h>
using namespace std;
#define maxn 500000 + 5
typedef long long ll;
int t[maxn], a[maxn], p[maxn];
int n;
ll ans;
int lowbit(int x)
{
return x & (-x);
}
void update(int x)
{
while (x <= n)
{
t[x] += 1;
x += lowbit(x);
}
}
ll query(int x)
{
ll sum = 0;
while (x)
{
sum += t[x];
x -= lowbit(x);
}
return sum;
}
bool cmp(int i, int j)
{
return a[i] < a[j];
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
p[i] = i;
}
stable_sort(p + 1, p + n + 1, cmp);
for (int i = 1; i <= n; i++)
{
update(p[i]);
ans += i - query(p[i]);
}
cout << ans;
return 0;
}