pku2299 Ultra-QuickSort

题目意思是求一个序列的逆序数,朴素的做法时间复杂度是O(n^2),其中 n < 500,000 ,结果不用说肯定是超时的;于是思路转向了O(nlogn)的算法,换言之离不开二分、树型结构等方法
这里用到的是杨挺的PDF《树状数组和线段树》里提到的方法:巧妙地将问题转化成类似求RMQ的问题,然后通过树状树组解决

1,将序列离散化,即序列内第k小的元素变成k,注意到题目已说明序列内无相同元素;离散化可通过排序和二分查找解决
2,将b[]初始化为0,遍历离散化后的序列a[1~n],每当向树状数组c[]插入a[i]前,根据c[]用O(logn)的时间求b[1]~b[a[i]]的和,即求得比a[i]先插入且比a[i]小的元素个数(放到序列上理解,就是排在a[i]前面且比a[i]小的元素个数),换算后可得比a[i]先插入且比a[i]大的元素个数,在遍历过程中将其累加到ans,即为答案
3,更新ans后置b[a[i]]为1,同时更新c[]的值(对c[]插入a[i])

排序和统计的复杂度都为O(nlogn),所以算法总复杂度是O(nlogn)

 

 

#include  < iostream >
#include 
< algorithm >
using   namespace  std;

#define  MAXN 500005
#define  clr(x) memset(x,0,sizeof(x))

int  a[MAXN],b[MAXN],c[MAXN],n;
__int64 ans;

int  find( int  x){
    
int  l = 1 ,r = n,m;
    
while (r >= l){
        m
= (l + r) / 2 ;
        
if (x < b[m])
            r
= m - 1 ;
        
else   if (x > b[m])
            l
= m + 1 ;
        
else
            
return  m;
    }
    
return   - 1 ;
}

void  discretization(){
    
int  i,t;
    
for (i = 1 ;i <= n;i ++ ){
        t
= find(a[i]);
        a[i]
= t;
    }
}

inline 
int  lowbit( int  x){
    
return  x & ( - x);
}

int  sum( int  k){ // 求sum(b[1]~b[k])
     int  i,res = 0 ;
    
for (i = k;i > 0 ;i -= lowbit(i))
        res
+= c[i];
    
return  res;
}

void  improve( int  k){ // 插入a[i]后b[a[i]]的值有修改
     int  i;
        
// 若对b[k]有修改,设影响到了c[p1]、c[p2]、 ,则p1=k,pi+1=pi + lowbit(pi)
     for (i = k;i <= n;i += lowbit(i))
        c[i]
++ ;
}



void  build(){ // 建立树状数组c[]
    clr(b);
    clr(c);
    
int  i,k;
    
for (i = 1 ;i <= n;i ++ ){
        k
= a[i];
        ans
+= i - sum(k) - 1 ;
        b[k]
= 1 ;
        improve(k);
    }
}

int  main(){
    
int  i;
    
while (scanf( " %d " , & n)  &&  n){
        
for (i = 1 ;i <= n;i ++ ){
            scanf(
" %d " ,a + i);
            b[i]
= a[i];
        }
        sort(b
+ 1 ,b + n + 1 );
        discretization();
// 离散化
        ans = 0 ;
        build();
        printf(
" %I64d\n " ,ans);
    }
    
return   0 ;
}

 

另附合并排序求逆序数代码:

 

#include  < iostream >
#include 
< algorithm >
using   namespace  std;

#define  MAXN 500001

int  a[MAXN],t[MAXN],n;
__int64 ans;

void  Merge( int  l, int  m, int  r){
    
int  i = 0 ,j = l,k = m + 1 ;
    
while (j <= &&  k <= r){
        
if (a[j] > a[k]){
            t[i
++ ] = a[k ++ ];
            ans
+= m - j + 1 ;
        }
        
else
            t[i
++ ] = a[j ++ ];
    }
    
while (j <= m)
        t[i
++ ] = a[j ++ ];
    
while (k <= r)
        t[i
++ ] = a[k ++ ];
    
for (j = 0 ;j < i;j ++ ){
        a[l
+ j] = t[j];
    }
}


void  Mergesort( int  l, int  r){
    
if (l < r){
        
int  m = (l + r) / 2 ;
        Mergesort(l,m);
        Mergesort(m
+ 1 ,r);
        Merge(l,m,r);
    }
}


int  main(){
    
int  i;
    
while (scanf( " %d " , & n)  &&  n){
        ans
= 0 ;
        
for (i = 0 ;i < n;i ++ )
            scanf(
" %d " ,a + i);
        Mergesort(
0 ,n - 1 );
        printf(
" %I64d\n " ,ans);
    }
    
return   0 ;
}

转载于:https://www.cnblogs.com/zgmf_x20a/archive/2008/10/22/1316566.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值