求逆序数(暴力,归并,树状数组)
来源:互联网 发布:10.2越狱后抹掉数据 编辑:程序博客网 时间:2024/05/20 20:22
求一个数列的逆序数(逆序数就是数中各位在它前<后>面有多少个数比它大<小>,求出这些元素个数之和。)
逆序对:数列a[1],a[2],a[3]…中的任意两个数a[i],a[j] (i<j),如果a[i]>a[j],那么我们就说这两个数构成了一个逆序对
逆序数:一个数列中逆序对的总数
如数列 3 5 4 8 2 6 9
(5,4)是一个逆序对,同样还有(3,2),(5,2),(4,2)等等
那么如何求得一个数列的逆序数呢?
方法1:直接暴力的一个个数,对于数列中的每一个数a[i],遍历数列中的数a[j](其中j<i),若a[i]<a[j],则逆序数加1,这样就能统计出该数列的逆序数总和
时间O(n*n)
方法2:
在合并的过程中是将两个相邻并且有序的序列合并成一个有序序列,如以下两个有序序列
Seq1:3 4 5
Seq2:2 6 8 9
合并成一个有序序:
Seq:2 3 4 5 6 8 9
对于序列seq1中的某个数a[i],序列seq2中的某个数a[j],如果a[i]<a[j],没有逆序数,如果a[i]>a[j],那么逆序数为seq1中a[i]后边元素的个数(包括a[i]),即len1-i+1,这样累加每次递归过程的逆序数,在完成整个递归过程之后,最后的累加和就是逆序的总数。
代码如下:
<span style="font-size:14px;">#include <iostream>using namespace std;#include <string.h>#include <stdio.h>#include <climits>#include <algorithm>#define m1 505000typedef long long LL;int date[m1],a[m1],b[m1];LL ans;void Mer( int *date, int left, int mid, int right ){ int i,j; i =0; for( j = left; j <= mid; j ++ ) b[i++]=date[j]; int len1=mid-left+1; i=0; for( j = mid+1; j <=right; j ++) a[i++]=date[j]; int len2 = right-mid; i=0;j=0; int k =left; while(i<len1&&j<len2&&k<=right) { if(b[i]<=a[j]) date[k++]=b[i++]; else{ date[k++]=a[j++]; ans+=(len1-i); } } while(i<len1) date[k++]=b[i++]; while(j<len2) date[k++]=a[j++];}void Merg( int *date, int left, int right ){ if( left < right ) { int m = ( left + right ) / 2; Merg(date, left, m ); Merg( date, m+1, right ); Mer( date, left, m, right ); }}int main(){ int n; while( scanf ( "%d", &n ) ,n ) { for( int i = 0; i < n; i ++ ) scanf ( "%d", &date[i] ); ans = 0; Merg( date, 0, n-1 ); printf("%lld\n",ans); } return 0;}</span>
方法3:采用树状数组的方法。
1.先对输入的数组离散化,使得各个元素比较接近,而不是离散的,
2.接着,运用树状数组的标准操作来累计数组的逆序数。
算法详细解释:《下列解释部分是转载》
1.解释为什么要有离散的这么一个过程?
刚开始以为999.999.999这么一个数字,对于int存储类型来说是足够了。
还有只有500000个数字,何必要离散化呢?
刚开始一直想不通,后来明白了,后面在运用树状数组操作的时候,
用到的树状数组C[i]是建立在一个有点像位存储的数组的基础之上的,
不是单纯的建立在输入数组之上。
比如输入一个9 1 0 5 4,那么C[i]树状数组的建立是在,
下标 0 1 2 3 4 5 6 7 8 9
数组 1 1 0 0 1 1 0 0 0 1
现在由于999999999这个数字相对于500000这个数字来说是很大的,
所以如果用数组位存储的话,那么需要999999999的空间来存储输入的数据。
这样是很浪费空间的,题目也是不允许的,所以这里想通过离散化操作,
使得离散化的结果可以更加的密集。
2. 怎么对这个输入的数组进行离散操作?
离散化是一种常用的技巧,有时数据范围太大,可以用来放缩到我们能处理的范围;
因为其中需排序的数的范围0---999 999 999;显然数组不肯能这么大;
而N的最大范围是500 000;故给出的数一定可以与1.。。。N建立一个一一映射;
①当然用map可以建立,效率可能低点;
②这里用一个结构体
struct Node
{
int v,ord;
}p[510000];和一个数组a[510000];
其中v就是原输入的值,ord是下标;然后对结构体按v从小到大排序;
此时,v和结构体的下标就是一个一一对应关系,而且满足原来的大小关系;
for(i=1;i<=N;i++) a[p[i].ord]=i;
然后a数组就存储了原来所有的大小信息;
比如 9 1 0 5 4 ------- 离散后aa数组就是 5 2 1 4 3;
具体的过程可以自己用笔写写就好了。
3. 离散之后,怎么使用离散后的结果数组来进行树状数组操作,计算出逆序数?
如果数据不是很大, 可以一个个插入到树状数组中,
每插入一个数, 统计比他小的数的个数,
对应的逆序为 i- getsum( aa[i] ),
其中 i 为当前已经插入的数的个数,
getsum( aa[i] )为比 aa[i] 小的数的个数,
i- sum( aa[i] ) 即比 aa[i] 大的个数, 即逆序的个数
但如果数据比较大,就必须采用离散化方法
假设输入的数组是9 1 0 5 4, 离散后的结果aa[] = {5,2,1,4,3};
在离散结果中间结果的基础上,那么其计算逆序数的过程是这么一个过程。
1,输入5, 调用upDate(5, 1),把第5位设置为1
1 2 3 4 5
0 0 0 0 1
计算1-5上比5小的数字存在么? 这里用到了树状数组的getSum(5) = 1操作,
现在用输入的下标1 - getSum(5) = 0 就可以得到对于5的逆序数为0。
2. 输入2, 调用upDate(2, 1),把第2位设置为1
1 2 3 4 5
0 1 0 0 1
计算1-2上比2小的数字存在么? 这里用到了树状数组的getSum(2) = 1操作,
现在用输入的下标2 - getSum(2) = 1 就可以得到对于2的逆序数为1。
3. 输入1, 调用upDate(1, 1),把第1位设置为1
1 2 3 4 5
1 1 0 0 1
计算1-1上比1小的数字存在么? 这里用到了树状数组的getSum(1) = 1操作,
现在用输入的下标 3 - getSum(1) = 2 就可以得到对于1的逆序数为2。
4. 输入4, 调用upDate(4, 1),把第5位设置为1
1 2 3 4 5
1 1 0 1 1
计算1-4上比4小的数字存在么? 这里用到了树状数组的getSum(4) = 3操作,
现在用输入的下标4 - getSum(4) = 1 就可以得到对于4的逆序数为1。
5. 输入3, 调用upDate(3, 1),把第3位设置为1
1 2 3 4 5
1 1 1 1 1
计算1-3上比3小的数字存在么? 这里用到了树状数组的getSum(3) = 3操作,
现在用输入的下标5 - getSum(3) = 2 就可以得到对于3的逆序数为2。
6. 0+1+2+1+2 = 6 这就是最后的逆序数
分析一下时间复杂度,首先用到快速排序,时间复杂度为O(NlogN),
后面是循环插入每一个数字,每次插入一个数字,分别调用一次upData()和getSum()
外循环N, upData()和getSum()时间O(logN) => 时间复杂度还是O(NlogN).
最后总的还是O(NlogN).
代码如下:
#include <iostream>#include <stdio.h>#include <algorithm>#include <string.h>using namespace std;typedef long long LL;struct point{ LL x; LL ord;}a[500005];LL b[500005];LL c[500005];bool cmp(const point a,const point b){ return a.x<b.x;}LL lowbit(int x){ return x&(-x);}void update(LL pos,LL num){ while(pos<=500005) { c[pos]+=num; pos+=lowbit(pos); }}LL sum(LL end1){ LL s=0; while(end1>0) { s+=c[end1]; end1-=lowbit(end1); } return s;}int main(){ LL n; while(scanf("%lld",&n)&&n!=0) { LL i; for(i=1;i<=n;i++) { scanf("%lld",&a[i].x); a[i].ord=i; } sort(a+1,a+n+1,cmp); memset(b,0,sizeof(b)); memset(c,0,sizeof(c)); for(i=1;i<=n;i++) b[a[i].ord]=i; LL ans=0; for(i=1;i<=n;i++) { update(b[i],1); ans+=(i-sum(b[i])); } printf("%lld\n",ans); } return 0;}
- 求逆序数(暴力,归并,树状数组)
- 归并排序&&树状数组求逆序数
- 归并排序 树状数组 求逆序数
- 树状数组(归并排序) 之 求逆序数nyoj117
- hdu 1394 Minimum Inversion Number 求逆序数(树状数组/归并排序/暴力)
- 归并求逆序数 树状数组求逆序数 线段树求逆序数
- [hdu]1394 Minimum Inversion Number -- 暴力求逆序、树状数组求逆序、线段树求逆序、归并排序求逆序
- 【树状数组or归并排序求逆序数】HDU 1394
- NYOJ 117 求逆序数 【树状数组】或【归并排序】
- nyoj117求逆序数(离散化+树状数组/归并排序)
- NYOJ-117 求逆序数(树状数组或归并排序)
- ACM--归并排序&&树状数组--nyoj 117--求逆序数
- 树状数组 和 归并排序 求逆序数
- 归并排序,树状数组,求逆序数 (openjudge 7662)
- 2299 求逆序数对 归并排序/树状数组
- POJ 2299 Ultra-QuickSort 【归并排序求逆序数 OR 树状数组求逆序数】
- 树状数组求逆序数(模板)
- poj3067(树状数组求逆序数)
- nc父类配置文件
- 堆排序(算法导论实现)
- 杭电 1234 开门人与关门人
- ***********ListView中Item处理点击Item进入另一页面***********
- sort 和qsort函数cmp函数各种写法
- 求逆序数(暴力,归并,树状数组)
- 在i.MX6Q上安装ubuntu(linaro)12.04
- Linux中的ulimit命令简介
- eclipse php 开发环境搭建记录
- 书中指标源码
- Residual block CAVLC semantics
- 1056. Mice and Rice (25)
- Windows下C语言查找文件例子
- 中兴openday活动