逆序对 (树状数组 | | 归并排序
数组前面的一个元素 大于等于 后面的一个元素就是一个逆序对;
树状数组可以快速求前缀和,利用这一特性,可以求逆序对个数,见下:
用数组c[ i ]记录数组a[ n ]中i这一元素出现的次数 ,当a[ n ]中元素较大时可以离散化处理。
将a[ n ]从a[n -1]到a[0] 依次存到树状数组中,每存一个,对存的元素i求一次c[i]的前缀和, 这就是当前已扫描过的比i小的元素的个数,由于a[n]是倒着扫描的,所以此时比i小的元素都对应一个逆序对,统计之。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; int c[600000]; int a[600000] ,b[600000]; int n; long long ans; int lowbit( int x){ return x & ( -x); } void updata( int x ,int v){ while( x<=n){ c[x] += v; x += lowbit( x); } } int getsum( int x){ int s=0; while( x>0){ s += c[ x]; x -= lowbit( x); } return s; } int main( ){ while( ~scanf( "%d" ,&n) ,n){ memset( c ,0 ,sizeof( c )); for( int i=0 ;i<n ;i++){ scanf( "%d" ,&a[i]); b[i] = a[i]; } sort( b ,b + n); //离散化处理 int sz = unique( b ,b+n) - b; ans = 0; for( int i=n-1 ;i >=0 ;i--){ int t = lower_bound( b ,b+sz ,a[i]) -b +1; //搜索对应的元素下标 ans += getsum( t) ; updata( t, 1); } printf( "%lld\n" ,ans); } return 0; }
归并排序求逆序对在lrj的高效算法设计一章学过,不过又把一些细节忘了:
1) 分治的参数x,y是左闭右开的,就是(n-1是数组末项下标 (0,n)->( 0,m ),(m ,n )
2) 分治条件y-x>1,因为y是开的,碰不到,y-x==1就是只有一个元素不能再分的情况了
3) 求逆序对时是 cnt += m-p,有半部分有一个小的左半部分没排的(m-p个)都是比他大的
#include <cstdio> #include <cstring> #include <algorithm> #include <iostream> using namespace std; int a[600000]; int b[600000]; int n; long long ans; void ergesort( int x ,int y){ // cout << x<<' '<<y<<endl; if( y - x <= 1)return; int m = x + (y - x)/2; ergesort( x ,m) ; ergesort( m ,y) ; int p = x, q =m ,i=x; while ( p < m || q < y){ if( q >=y || p<m && a[p] < a[q]) b[i++] = a[p++]; else b[i++] = a[q++] ,ans+= m-p; } for( int i=x ; i<y ;i++) a[ i] = b[ i]; return ; } int main( ){ while( ~scanf( "%d" ,&n) ,n){ ans=0; for( int i=0 ;i<n ;i++){ scanf( "%d" ,&a[i]); } ergesort( 0 ,n); // for( int i=0 ;i<n ;i++)printf("%d ",a[i]); printf( "%lld\n",ans); } return 0; }