11087 统计逆序对
来源:互联网 发布:92kaifa 域名秀 编辑:程序博客网 时间:2024/06/05 23:53
11087 统计逆序对(必做)
时间限制:1000MS 内存限制:65535K
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC;VC;JAVA
Description
设a[0…n-1]是一个包含n个数的数组,若在i<j的情况下,有a[i]>a[j],则称(i, j)为a数组的一个逆序对(inversion)。比如 <2,3,8,6,1> 有5个逆序对。请采用类似“合并排序算法”的分治思路以O(nlogn)的效率来实现逆序对的统计。一个n个元素序列的逆序对个数由三部分构成:(1)它的左半部分逆序对的个数,(2)加上右半部分逆序对的个数,(3)再加上左半部分元素大于右半部分元素的数量。其中前两部分(1)和(2)由递归来实现。要保证算法最后效率O(nlogn),第三部分(3)应该如何实现?此题请勿采用O(n^2)的简单枚举算法来实现。并思考如下问题:(1)怎样的数组含有最多的逆序对?最多的又是多少个呢?(2)插入排序的运行时间和数组中逆序对的个数有关系吗?什么关系?
输入格式
第一行:n,表示接下来要输入n个元素,n不超过10000。第二行:n个元素序列。
输出格式
逆序对的个数。
输入样例
52 3 8 6 1
输出样例
5
提示
此题看着简单啊,但陷入此坑的同学不少……。题目初看简单,但仔细分析有点意思!一、算法整体思路和框架确定n个元素的逆序对数目在最坏情况O(nlogn)的算法,可以考虑仿归并排序中的分治算法:一个序列的逆序对个数由三部分构成:(1)它的左半部分逆序对的个数,(2)加上右半部分逆序对的个数,(3)再加上左半部分元素大于右半部分元素的数量。其中(1)和(2)由递归来实现,(3)计算后加入。伪代码如下://count_inversion():计算逆序对数量int count_inversion(int array[], int low, int high){ int count = 0, middle; if(low < high) { middle = low + (high - low) / 2; count += count_inversion(array, low, middle); //加入:对左段的逆序对的递归计数 count += count_inversion(array, middle + 1, high); //加入:对右段的逆序对的递归计数 count += merge_inversion(array, low, middle, high); //加入上面提到的第三种情形的计数 } return count;}二、分治算法的效率分析,是否能达到题目所要求的O(nlogn)?分析一下算法效率,题目要求算法效率为O(nlogn),假设T(n)为n个元素逆序对统计的分治算法时间。则T(1)=1, T(n) = 2T(n/2) + O(?),要使得算法整个效率T(n)=O(nlogn),则上述第(3)步只能是O(n)。那又怎么能在O(n)时间内算出“左半部分元素大于右半部分元素的数量”?一般方法,左段任何一个元素都要和右段任何一个元素比较,然后得到第(3)步的逆序对个数,这需要O(n^2)啊,O(n)做不到的啊。但是,若左右段元素都各自段内有序,那可以做到O(n),只需要逐个比较左右段段首元素即可。这个过程和“合并排序算法”的归并的过程很类似,只是在归并的时候加入了计数的操作。即左右段“队首元素”进行比较,* 如果来自于左段的队首大于右段的队首,则记数加上左段元素个数(为什么是加上左段元素个数呢?因为左段段首是左段中最小的了,最小的都比右段段首大,那左段所有元素都大于右段段首元素的,因此计数应该是加上左段中所有的元素个数),且同时记录下小的这个数(即右段段首);* 否则记数不变,但依然记下小的这个数(即左段段首)。* 这个“左右两段的队首元素比较”的过程一直持续下去,直到所有元素都记录下来,也即合并过程结束。这第(3)步做完,同时左段和右段也合并有序了,并将合并后有序数组覆盖原数组。也就是说第(3)步会改变数的顺序,在统计的同时也进行排序了。三、疑问?有人问,数的顺序都改变了,那原数序列和合并排序后的序列,去求逆序对个数还一样的吗?这样求解是对的吗? —————— 回答: 是一样的!1, 首先你问的这个问题表述就不准确,其实并不是“合并排序后的序列去求逆序对个数”,而是在合并之前先让左右段成序,再统计第(3)步,而后再合并排序成一个有序序列的。若全都排好了再统计逆序对个数,那当然是不一样的了。2, 其次左右序列成序并不影响第(3)步(即统计左段元素比右段元素大的数量)。而在整个序列排序之前,第(3)步又已做完,序列再怎么改变此时已经不影响逆序对统计数了,因为已经统计完了。3, 第(3)步要O(n)完成,还就得左右段都成序了才行,若左右段不成序,就做不到O(n)完成第(3)步,那得两重循环O(n^2)才能做第(3)步了,进一步,若第(3)步完成需O(n^2),那整个算法也无法做到O(nlogn)了。到此,分析结束,你觉得这个分析是不是比较有意思?也就是说这个问题的求解是嵌在典型的合并排序算法之中的,但不是嵌套而是交错进行。四、具体实例分析好吧,如果还不清楚,我们以题上的数据实例来分析吧。序列: 2 3 8 6 1分为: 2 3 8 | 6 1对左段:(2 3 8) 计数count将增加0(此处省略若干递归的过程和文字);对右段:(6 1) 计数count将增加(6)(1)的左段个数,即增加1,这里是递归计算得到的,并且递归结束后右段调整为:1 6对左右段,此时序列为(2 3 8)(1 6), * 左段段首2大于右段段首1,所以左段所有元素都大于右段段首1,计数count增加左段段长,即增3,且同时记录下小的段首元素1,此时序列为(2 3 8)(6); * 记下2,但计数值不增加,此时序列为(3 8)(6); * 记下3,但计数值不增加,此时序列为(8)(6); * 记下6,计数值count增加左段元素个数,这里即为1; * 记下8,但计数值不增加,此时序列为空了。 * 记下的数形成新的序列:1 2 3 6 8,也就是排序之后的形式了。返回计数值count:1+3+1,即返回5。------------------------------------------------------------------------------------------------五、回答题目的问题最后,回答本题提出的需要思考的问题:(1)一个逆序的序列含有最多逆序对,最多为:1+2+...+(n-1)=n(n-1)/2。 顺序的序列含有最少逆序对,数量为0。(2)插入排序的运行时间和数组中逆序对个数有关, 最好的情况,原序列已经成顺序,则插入排序的代价是O(n); 最坏的情况,原序列为逆序,插入排序的代价为O(n^2); 平均情况下也是O(n^2)的。 每一个逆序对都将引起插入过程中的一次比较及后续数的挪位,因此说数组中逆序对个数越少,插入排序算法性能就越好。
#include <iostream>using namespace std;int merge_inversion(int a[],int low ,int middle ,int high){ int sum = 0; int llen = middle-low+1; int rlen = high - middle; int a1[llen+1] , a2[rlen+1]; /* 左右两段分别赋值给两个数组; */ for(int i=0;i<llen;i++){ a1[i] = a[low+i]; } for(int i=0;i<rlen;i++){ a2[i] = a[middle+i+1]; } a1[llen] = 999; a2[rlen] =999;//两个临界点必须要相同,不然下面的判断会出错 /* 开始比较 这里需要k是因为比较过程中需要将原数组的顺序变换 用k去判断循环就是要让比较后搞定原数组的排序,执行之后数组a将是从小到大有序的 */ for(int i=0,j=0,k=low;k<=high;k++){ if(a1[i]>a2[j]){//当两个相等时就是边界啦 sum += llen - i; a[k] = a2[j]; j++; } else{ a[k] = a1[i]; i++; } } return sum;}int count_inversion(int a[],int low,int high){ int count = 0,middle; if(low<high){ middle = low+(high-low)/2; count += count_inversion(a,low,middle); count += count_inversion(a,middle+1,high); count += merge_inversion(a,low,middle,high); } return count;}int main(){ int n; cin>>n; int a[n]; for(int i=0;i<n;i++){ cin>>a[i]; } cout<<count_inversion(a,0,n-1); return 0;}
0 0
- 11087 统计逆序对
- 统计逆序对
- 统计数组中的逆序对
- 统计数组的逆序对
- 【51Nod1779】逆序对统计
- 11087 统计逆序对(递归(分治),交错)
- Jzoj1020 逆序对统计 ≈ Bzoj3295 动态逆序对
- 数组中逆序对统计方法
- 统计逆序对--函数式线段树
- POJ 2299 - Ultra-QuickSort 统计逆序对
- 统计数组中的逆序对的数量
- 树状数组入门—逆序对统计
- 归并排序以及逆序对统计
- 51Nod-1779-逆序对统计
- 顺序统计树求逆序对 O(nlgn)
- HDU_4911_Inversion_归并统计逆序对_杭电多校A题
- (p196)利用顺序统计树求逆序对
- [NOIP模拟][dp][codevs]p1401逆序对统计
- Android网络编程六:(2)Universal-Image-Loader之缓存策略
- XMPP
- 深入理解Hibernate的三种状态
- Android 超高仿微信图片选择器 图片该这么加载
- 15.11.25学习笔记 炫酷的滚动视图
- 11087 统计逆序对
- WebViewJavascriptBridge 原理分析
- java 集合 HashMap
- imageBeffer绘制图形
- 程序员不可不知的英文词汇(4)
- 匿名内部类
- 知名App第三方库汇总2014.10
- 创建及使用静态库(.a)的步骤整理
- token和deviceId