题目如下:
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; }