openjudge 求逆序对数

题目如下:

4:求逆序对数
查看 提交 统计 提问
总时间限制: 1000ms 内存限制: 65536kB
描述
对于一个长度为N的整数序列A,满足1<=iAj的数对(i,j)称为整数序列A的一个逆序

请求出整数序列A的所有逆序对个数

输入
输入包含多组测试数据,每组测试数据有两行
第一行为整数N(1<=N<=20000),当输入0时结束
第二行为N个整数,表示长为N的整数序列
输出
每组数据对应一行,输出逆序对的个数
样例输入
5
1 2 3 4 5
5
5 4 3 2 1
1
1
0
样例输出
0
10
0


==========================================================================

非常经典的题目。显而易见的算法(学名:naive algorithm ^_^)是两层循环,第一层循环逐个扫描序列的元素,第二层循环逐个扫描当前元素之后的所有元素,看是否形成逆序对,并统计逆序对的数目,这个算法的时间复杂度是O(n^2)。


事实上还有时间复杂度为O(nlogn)的算法。考虑归并排序的某一合并步骤,例如3,4,6,10与2,5,7,8合并,3>2,由于左右半边各自有序,所以左半边3之后的元素均>3>2,因此都会与2形成逆序对,所以目前有4个逆序对(3,2)(4,2)(6,2)(10,2),把2划去,两个子序列变为3,4,6,10与5,7,8,重复以上过程,即可统计出“新增”逆序对的数目delta。这个过程的时间复杂度为O(n)。


为什么说是“新增”呢?因为在合并3,4,6,10与2,5,7,8之前,两个子序列都未必是升序(但是不管如何调整子序列内部元素的顺序,都不会影响delta值),亦即,它们的逆序对数目不一定是0,因此我们有递推公式:Inv(n)=2Inv(n/2)+delta,其中计算delta的过程时间复杂度为O(n)。根据Master Theorem,我们知道整个过程的时间复杂度为O(nlogn)。


这是一个递归的过程,只要对归并排序代码稍作修改即可。

===========================================================================


代码清单:

#include <iostream>
#include <cstdio>
using namespace std;

#define MAXN 20005

int a[MAXN];
int tempa[MAXN];

//归并排序之合并函数,时间复杂度O(n)
int Merge(int a[], int tempa[], int left, int right, int mid)
{
	int delta=0;	//用于记录合并后新增的逆序对数目

	for (int i=left; i<=right; ++i)
	{
		tempa[i]=a[i];
	}

	int cursor=left;
	int index1=left;	//left half
	int index2=mid+1;	//right half

	while (index1<=mid && index2<=right)
	{
		if (tempa[index1]<=tempa[index2])	//左边的元素=右边的元素,先拷贝左边的元素,由此可知归并排序是稳定的
		{
			a[cursor++]=tempa[index1++];
		}
		else
		{
			a[cursor++]=tempa[index2++];
			//在这里统计“新增”的逆序对
			//举个例子,3,4,6,10和2,5,7,8归并,观察到3>2,由于左右两部分都是有序的,所以左半部分3之后的元素都>2,与2形成了逆序,逆序对个数为4
			delta += (mid-index1+1);
		}
	}

	//接下来把没搬完的搬过去
	while (index1<=mid)
	{
		a[cursor++]=tempa[index1++];
	}

	while (index2<=right)
	{
		a[cursor++]=tempa[index2++];
	}

	return delta;	//返回“新增”逆序对数目
}

//归并排序
//好的,归并排序已经写好,下面加入计算逆序对数目的代码。cnt就是逆序对的数目。
void MergeSort_CountInv(int a[], int tempa[], int left, int right, int *cnt)
{
	int mid=(left+right)/2;	//分割点
	
	if (left<right)	//有至少两个元素才递归调用
	{
		//对left half和right half分别进行递归调用
		MergeSort_CountInv(a, tempa, left, mid, cnt);
		MergeSort_CountInv(a, tempa, mid+1, right, cnt);

		//归并过程
		(*cnt) += Merge(a, tempa, left, right, mid);
	}
}


int main()
{
	freopen("D:\\in.txt", "r", stdin);
	freopen("D:\\out.txt", "w", stdout);

	int cnt;
	
	int n;
	while (1)
	{
		scanf("%d", &n);

		if (n==0)	break;

		for (int i=0; i<n; ++i)
		{
			scanf("%d", &a[i]);
		}

		cnt=0;
		MergeSort_CountInv(a, tempa, 0, n-1, &cnt);
		printf("%d\n", cnt);
	}

	return 0;
}

你可能感兴趣的:(排序,递归,归并排序,OpenJudge,逆序对数目)