洛谷 p1908 逆序对 归并排序

题目描述

猫猫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,超时了。

TLE

代码如下:

#include
#include
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(Nlog2​N) 。树状数组解法+离散化等本渣之后再啃啃
#include
#include
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;
}

你可能感兴趣的:(算法,分治法,归并排序)