题目描述
猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中ai>aj且i
输入输出格式
输入格式:
第一行,一个数n,表示序列中有n个数。
第二行n个数,表示给定的序列。序列中每个数字不超过 10^9
输出格式:
给定序列中逆序对的数目。
输入输出样例
输入样例#1:
6
5 4 2 6 3 1
输出样例#1:
11
说明
对于25%的数据, n < 2500
对于 50%的数据,n ≤ 4×10^4。
对于所有数据, n ≤5×10^5
请使用较快的输入输出
应该不会n方过50万吧 by chen_zhe
题解
看完这个题目, (渣渣) 自己一开始想到的就是双重for循环,判断,如果小的话就计数+1。几分钟就写完了,莫名觉得很简单。把代码提交上去了结果只过5/20。TLE,超时了。
![](https://i-blog.csdnimg.cn/blog_migrate/0dc9c5ca87e79aaf15a57642a2248537.png)
代码如下:
#include<iostream>
#include<cstdio>
using namespace std;
int a[500100];
int main()
{
long long ans=0;
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (a[i] < a[j]) {
ans++;
}
}
}
printf("%lld",ans);
return 0;
}
要想通过所有的测试样例,必须选择更优的算法,减少时间复杂度。于是去拜读了dalao们的题解。本题可以使用至少两种方法。
- 归并排序
- 树状数组解法+离散化
归并排序方法,勉强理解,第二种方法到现在我还没看懂。自己的理解力真差。
官方解释:一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序 :如果想将序列按从小到大排序,每次将左右两个子区间(各区间内的也按从小到大排序)按从小到大进行排序 合并为一个大区间 。在合并的同时,记录存在的逆序的数的个数——统计右边区间每一个数分别会与左边区间产生多少逆序对 (个人理解夹杂别人的解释,可能有误)
- 存在在某个时候, (借dalao的例子来解释)
左区间: 5 6 7 下标为i
右区间: 1 2 9 下标为j - // 这个时候我们进行合并:
- step 1:由于 5>1,所以产生了逆序对,这里,我们发现,左区间所有还没有被合并的数都比 1 大,所以1与左区间所有元素共产生了 3 个逆序对(即tot_numleft-i+1对),统计答案并合并 1
- step 2:由于 5>2,由上产生了3对逆序对,统计答案并合并 2
- step 3:由于 5<9, 没有逆序对产生,右区间下标 j++
- step 4:由于 6<9, 没有逆序对产生,右区间下标 j++
- step 5:由于 7<9, 没有逆序对产生,右区间下标 j++
- step 6:由于右区间已经结束,正常执行合并左区间剩余,结束
- //PS: tot_numleft=3,即左区间总元素个数
参考了学无止境 的博客的题解及代码,注意答案会爆int !我们需要使用long long 存,如果使用 printf 输出,那么可别忘了用%lld 。
附上归并排序代码, 时间复杂度O(Nlog2N) 。树状数组解法+离散化等本渣之后再啃啃
#include<iostream>
#include<cstdio>
using namespace std;
int a[500100];
int backupArray[500100];
long long ans;
void mid_sort(int left,int right) {
if (left == right) { //直到左右两个节点为空时,结束划分子区间
return;
}
int mid = (left + right) / 2;
int i = left, j = mid + 1;
int k = left;
mid_sort(i, mid);
mid_sort(mid + 1, right);
while (i <= mid && j <= right) {
if (a[i] <= a[j]) {
backupArray[k++] = a[i++];
}
else {
backupArray[k++] = a[j++];
ans += mid - i + 1; //统计答案
}
}
while (i <= mid) {
backupArray[k++] = a[i++];
}
while (j <= right) {
backupArray[k++] = a[j++];
}
for (int i = left; i <= right; i++) {
a[i] = backupArray[i];
//归并,把已经排号序的,重新赋值给原数组
}
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
mid_sort(1, n);
printf("%lld", ans);
//system("pause");
return 0;
}