POJ 2299 Ultra-QuickSort(求逆序数)

Ultra-QuickSort

题目链接:

http://poj.org/problem?id=2299

解题思路:

树状数组实际上就是一个数组,只不过它的每个元素保存了跟原来数组的一些元素相关的结合值。

若A为原数组,定义数组C为树状数组。C数组中元素C[ i ]表示A[ i –lowbit( i ) + 1]至A[ i ]的结合值。

lowbit(i)是i的二进制中最后一个不为零的位数的2次方,可以这样计算

lowbit(i)=x&(-x)

当想要查询一个sum(n)时,可以依据如下算法即可:
step1: 令sum = 0,转第二步;
step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;
step3:  令n = n – lowbit(n),转第二步。

n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。
 
修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。所以修改算法如下(给某个结点i加上x):
step1: 当i > n时,算法结束,否则转第二步;
step2: Ci = Ci + x, i = i + lowbit(i)转第一步。
i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。

求逆序的思路:

可以把数一个个插入到树状数组中, 每插入一个数, 统计比他小的数的个数,对应的逆序为 i- getsum( data[i] ),其中 i 为当前已经插入的数的个数, getsum( data[i] )为比 data[i] 小的数的个数,i- getsum( data[i] ) 即比 data[i] 大的个数, 即逆序的个数。最后需要把所有逆序数求和,就是在插入的过程中边插入边求和。

AC代码(树状数组):

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

typedef long long ll;
const int N = 500005;
int n;
struct node{
    int pos,val;
}no[N];
int reflect[N];
int c[N];

bool cmp(const node &a,const node &b){
    return a.val < b.val;
}

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

void update(int x){
    while(x <= n){
        c[x] += 1;
        x += lowbit(x);
    }
}

int sum(int x){
    int ans = 0;
    while(x){
        ans += c[x];
        x -= lowbit(x);
    }
    return ans;
}

int main(){
    while(scanf("%d",&n),n){
        for(int i = 1; i <= n; i++){
            scanf("%d",&no[i].val);
            no[i].pos = i;
        }
        sort(no+1,no+n+1,cmp);
        for(int i = 1; i <= n; i++)
            reflect[no[i].pos] = i;//离散化
        for(int i = 1; i <= n; i++)
            c[i] = 0;//初始化
        ll ans = 0;
        for(int i = 1; i <= n; i++){
            update(reflect[i]);
            ans += (ll)(i - sum(reflect[i]));
        }
        printf("%lld\n",ans);
    }
    return 0;
}

解题思路:

归并排序是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为 
*  若干个子序列,每个子序列是有序的,然后再把有序的子序列合并为整体有序序列 
*  归并排序是分治算法的一个典型的应用,而且是稳定的一种排序,这题利用归并排序 
*  的过程中,计算每个小区间的逆序数,进而得到大区间的逆序数。那么,问题就解决了。 

即:对于数列[l, mid] [mid + 1, r]的合并,i 从l开始循环,j从mid + 1开始循环,如果遇到a[i] > a[j] 则出现逆序,可以将a[j]放入辅助数组,同时j++,那么和a[j]逆序的数就有mid-i+1个,因为序列是有序的[i, mid]的所有的数都是大于a[j]的。代码如下。有重温了归并的写法与思想。。

AC代码(归并排序):

#include <iostream>
#include <cstdio>
#include <algorithm>
#define INF 0xfffffff
using namespace std;

typedef long long ll;
const int N = 500005;
int n;
int a[N],L[N],R[N];
ll ans;

void Merge(int a[],int l,int mid,int r){
    int n1 = mid-l+1;
    int n2 = r-mid;
    int i,j,k;
    for(i = 1; i <= n1; ++i)
        L[i] = a[l+i-1];
    for(i = 1; i <= n2 ; ++i)
        R[i] = a[mid+i];
    L[n1+1] = INF;
    R[n2+1] = INF;
    i = 1;
    j = 1;
    for(k = l; k <= r; ++k){
        if(L[i] <= R[j]){
            a[k] = L[i];
            i++;
        }
        else{
            a[k] = R[j];
            j++;
            ans += (ll)(n1-i+1);
        }
    }
}

void Merge_sort(int a[],int l,int r){
    if(l < r){
        int mid = (l+r)>>1;
        Merge_sort(a,l,mid);
        Merge_sort(a,mid+1,r);
        Merge(a,l,mid,r);
    }
}

int main(){
    while(scanf("%d",&n),n){
        for(int i = 1; i <= n; ++i)
            scanf("%d",&a[i]);
        ans = 0;
        Merge_sort(a,1,n);
        printf("%lld\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值