【题目描述】
在Internet上的搜索引擎经常需要对信息进行比较,比如可以通过某个人对一些事物的排名来估计他(或她)对各种不同信息的兴趣,从而实现个性化的服务。
对于不同的排名结果可以用逆序来评价它们之间的差异。考虑1,2,…,n的排列i1,i2,…,in,如果其中存在j,k,满足 j < k 且 ij > ik, 那么就称(ij,ik)是这个排列的一个逆序。
一个排列含有逆序的个数称为这个排列的逆序数。例如排列 263451 含有8个逆序(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),因此该排列的逆序数就是8。显然,由1,2,…,n 构成的所有n!个排列中,最小的逆序数是0,对应的排列就是1,2,…,n;最大的逆序数是n(n-1)/2,对应
的排列就是n,(n-1),…,2,1。逆序数越大的排列与原始排列的差异度就越大。
现给定1,2,…,n的一个排列,求它的逆序数。
输入:第一行是一个整数n,表示该排列有n个数(n <= 100000)。第二行是n个不同的正整数,之间以空格隔开,表示该排列。
输出:该排列的逆序数。
【题目提示】
1. 利用二分归并排序算法(分治);
2. 注意结果可能超过int的范围,需要用long long存储。
【题目分析】
很多人要问了,到底啥是逆序?
简单来说:
现有一列数未经过排序:263451。我们可以观察到:2比1要大,可是1却在队尾。所以(2,1)就是一个逆序对,那我们就可以得出如题目所示的(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1)这些逆序对。共8个,就输出了8。
我一开始的思路:暴力出奇迹。
#include<iostream> using namespace std; int a[100001]; int main() { ios::sync_with_stdio(false); int i,n,j,ans=0; cin>>n; for(i=1;i<=n;++i) cin>>a[i]; for(i=1;i<=n;i++) { for(j=i+1;j<=n;j++) { if(a[i]>a[j]&&i<j) ans++; } } cout<<ans; }
直接写了如上代码,可是Time Limit Exceeded(6分)。我的妈呀这红红的几个单词直接就打破了我的自尊(开玩笑)
回头看了一眼提示,要用归并排序...都没听过这种排序...(微笑中透露着***)
经过老师的讲解,我倒是明白了归并是啥东西:
(这**不就是二分升级为了二分排序吗...)
(附上归并模板)
//归并排序 inline void merge(int left,int mid,int right) { int i=left,j=mid+1,k=left; int b[n]; while(i<=mid&&j<=right) { if(a[i]<=a[j])b[k++]=a[i++]; else b[k++]=a[j++]; } while(i<=mid)b[k++]=a[i++]; while(j<=right)b[k++]=a[j++]; for(i=1;i<=n;i++) a[i]=b[i]; } void mergesort(int a[],int left,int right) { if(left>right)return; int mid=(left+right)/2; mergesort(a,left,mid);//分治 mergesort(a,mid+1,right); merge(a,left,mid,right); }
为什么这种方法就比暴力快呢?
在你将两个有序队列归并为一个有序队列的进行过程中,你就可以进行答案的统计了。
(AC代码)
#include<stdio.h> long long ans,a[100001],b[100001],n; void merge(int left,int mid,int right) { int i=left,j=mid+1,k=left; while(i<=mid&&j<=right) { if(a[i]<a[j])b[k++]=a[i++]; else if(a[i]>a[j]) { ans+=j-k; b[k++]=a[j++]; } else b[k++]=a[i++]; } while(i<=mid)b[k++]=a[i++]; while(j<=right)b[k++]=a[j++]; for(i=left;i<=right;i++) a[i]=b[i]; } void mergesort(int left,int right) { if(left>right)return; int mid=(left+right)/2; if(left<right) { mergesort(left,mid);//分治 mergesort(mid+1,right); merge(left,mid,right); } return ; } int main() { scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); mergesort(1,n); printf("%lld",ans); return 0; }
通过如上代码,不难看出,边归并边排序的过程才是最节约时间的。
超时代码的时间复杂度为O(n²),而AC代码的时间复杂度为O(nlogn)
来一张图看看:
设有一队列456 113 342 9679 123 31 315
我要用归并排序来排序这个队列。
首先将此队列用二分法分成两个队列,再排序,得到两个有序队列:
1. 113 342 456
2. 31 123 315 9679(这四个数在之前无序队列里的排序肯定比上一个队列的排序大)
模拟一下:首先创建一个空队列,用来存储排好序的队列。31和113比,31小,先入队,ans不加(不满足逆序条件)。123和113比,113入队,但在这之前,31已经先入队,且31的原序数比113大,所以ans++。123和342比,123先入队,ans不加(不满足逆序条件)。315和342比,315先入队,ans不加(不满足逆序条件)。342和9679比,342先入队,但在342入队之前,123 315 31均已先入队,且在原无序队列里的序数比342大,数字比342小,故ans+=3,456同理。
故求出这个无序队列的逆序数:7。
再看这道题,是不是更清晰明了了呢?