POJ 2299 Ultra-QuickSort【求逆序数:归并排序|树状数组】

来源:互联网 发布:最新影视软件 编辑:程序博客网 时间:2024/05/01 10:13

题目点这

注意答案是long long型,然后注意相同数据的情况,如

3

1 1 1应当输出0

归并排序法【nlog(n)】求解:归并过程中如果a[indexA] > a[indexB] ,那么对于a[indexB]的逆序数有 mid - indexA + 1【包含自身】,如

下标:     0   1   2

a[indexA]:3    4    5

a[indexB]:2    1    3

那么对于a[indexB]里的2,有3个和它逆序的:3,4,5,合并之后2和345的逆序就为0了,因此不重复

//归并法求逆序数 3684K 375MS#include<stdio.h>#define ll long longll ans;int a[500001];int temp[500001];void Merge(int *arr,int first,int mid,int last){    int indexA = first;    int indexB = mid+1;    int k = 0;    while(indexA <= mid && indexB <= last)    {        if(arr[indexA] <= arr[indexB])        {            temp[k++] = arr[indexA];            indexA++;        }else        {            temp[k++] = arr[indexB];            indexB++;            ans += mid-indexA+1;    //这里是核心        }    }    while(indexA <= mid)    {        temp[k++] = arr[indexA];        indexA++;    }    while(indexB <= last)    {        temp[k++] = arr[indexB];        indexB++;    }    int index = 0;    for(int i=0;i<k;i++)    {        arr[first+index] = temp[i];        index++;    }}void mergeSort(int *arr,int first,int last){    if(first < last)    {        int mid = (first+last)/2;        mergeSort(arr,first,mid);        mergeSort(arr,mid+1,last);        Merge(arr,first,mid,last);    }}int main(){  //  freopen("in.txt","r",stdin);    int n;    while(scanf("%d",&n)!=EOF,n)    {        ans = 0L;        for(int i=0;i<n;i++)        {            scanf("%d",&a[i]);        }        mergeSort(a,0,n-1);        printf("%lld\n",ans);    }    return 0;}

精简版

#include <cstdio>#include <iostream>#include <algorithm>using namespace std;#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )#define LL long long#define RE freopen("1.in","r",stdin)#define WE freopen("1.out","w",stdout)#define NMAX 500002int tmp[NMAX],a[NMAX];LL ans;void Merge(int s[],int low,int mid,int high){    int i=low,j=mid+1,index=0;    while(i<=mid&&j<=high)    {        if(s[i]<s[j])   tmp[index++]=s[i++];        else tmp[index++]=s[j++],ans+=mid-i+1;    }    while(i<=mid)        tmp[index++]=s[i++];    while(j<=high)        tmp[index++]=s[j++];    int pos=low;    for(int w=0;w<index;w++)        s[pos++]=tmp[w];}void Msort(int s[],int low,int high){    if(low<high)    {        int mid=(low+high)>>1;        Msort(s,low,mid);        Msort(s,mid+1,high);        Merge(s,low,mid,high);    }}int main(){    int n;    //RE;    while(cin>>n,n)    {        ans = 0;        REP(i,n)            cin>>a[i];        Msort(a,0,n-1);        cout<<ans<<endl;    }}


法②  树状数组+离散化【nlog(n)】:

需要离散化是因为a[i]最大是999,999,999,而树状数组里的c[i]像标记数组那样,针对本题下标可能到999,999,999,太大。所以把输入的a[i]根据下标映射到[1,500000]。

struct node
{
    int pos;//原下标
    int val;//原值
}in[NMAX];

pos保存输入数据的原下标,val保存原值,输入时pos = i;

降序排序后

 for(int i=1;i<=n;i++)

{
            r[in[i].pos] = i;

}

r数组保存离散后的情况

in数组排序后 r [ 排序后数据的下标 ] = i ;使 r [ 最大的那个数的下标] = 1,r[ 次大数的下标 ] = 2,……

r[]数组保存下标,

r[i]:原数据中,下标为i的数排第几,如果是升序就是第几小,降序就第几大

遍历r的话,等同于按排序前的顺序遍历原数据,r[i]为它的相对大小,也对应着排序前数据的下标,所以in[r[i]].val 为原值(根据排序后的大小关系)。


求逆序数只要保证有大小关系就不需要管数据具体大小,所以排序保证大小关系,之后的c数组也就跟数据大小无关了

pos:1 2 3            4
val:4 3 9999999 999 
排序后
pos: 3           4      1     2
val: 9999999 999   4     3

-----离散Begin-----
r[3] = 1; 
r[4] = 2;
r[1] = 3;
r[2] = 4;
-----离散End-----

然后对离散后的数组r顺序遍历,更新值+1
r[1] = 3; in[r[1]].val = in[3].val = 9999999
r[2] = 4; in[r[2]].val = in[4].val = 999
r[3] = 1; in[r[3]].val = in[1].val = 4
r[4] = 2; in[r[4]].val = in[2].val = 3


c[3] += 1...父节点+=1
c[4] += 1...父节点+=1
c[1] += 1...父节点+=1
c[2] += 1...父节点+=1

c[3]+=1表示最大值那个节点+1,然后c[4]:次大值那个节点+1

 每插入一个数, 统计比他小的数的个数,对应的逆序为 i- getsum( r[i] ),

 i 为当前已经插入的数的个数,

 getsum( r[i] ) 为比 r[i] 小的数的个数,

  i- sum( r[i] )  即比 r[i] 大的个数, 即逆序的个数


再给个后来复习逆序数时加的例子:

输入数据:

val:    55   33   22 11    44
pos:   1     2     3     4      5


排序后:
i: 1       2        3       4      5
a[i].val: 11     22    33     44     55
a[i].pos: 4       3       2      5       1


r[1]=5:下标为1的数据排第5,即55是第5小,也就是第1大
r[5]=4:44是第4小
r[2]=3
r[3]=2
r[4]=1

顺序遍历:r[1]=5,r[2]=3,r[3]=2,r[4]=1,r[5]=4

c数组下标:1 2 3 4 5   getsum(r[1])=getsum(5)=1            i-getsum()=1-1=0
c[i]:                         1

c数组下标1 2 3 4 5   getsum(r[2])=getsum(3)=1            i-getsum()=2-1=1
c[i]:                    1 1 1

c数组下标1 2 3 4 5  getsum(r[3])=getsum(2)=1             i-getsum()=3-1=2
c[i]:                 1 1 2 1

c数组下标1 2 3 4 5  getsum(r[4])=getsum(1)=1             i-getsum()=4-1=3
c[i]:              1 2 1 3 1

c数组下标1 2 3 4 5   getsum(r[5])=getsum(4)=4            i-getsum()=5-4=1
c[i]:              1 2 1 4 1


//树状数组+离散化求逆序数 7408K516MS#include<stdio.h>#include<algorithm>#include<string.h>using namespace std;#define ll long long#define NMAX 500001ll ans;struct node{    int pos;//原下标    int val;//原值}in[NMAX];int r[NMAX],c[NMAX];int n;int lowbit(int x){    return x&(-x);}void update(int x,int num){    while(x<=n)    {        c[x] += num;        x += lowbit(x);    }}int getSum(int x){    int sum=0;    while(x>0)    {        sum += c[x];        x -= lowbit(x);    }    return sum;}int cmp(node a,node b){    return a.val < b.val;//升序}int main(){    //freopen("in.txt","r",stdin);    while(scanf("%d",&n)!=EOF,n)    {        ans = 0L;        for(int i=1;i<=n;i++)        {            scanf("%d",&in[i].val);            in[i].pos = i;        }        sort(in+1,in+n+1,cmp);        for(int i=1;i<=n;i++)        {            r[in[i].pos] = i;        }        memset(c,0,sizeof(c));        for(int i=1;i<=n;i++)        {            update(r[i],1);            ans += i - getSum(r[i]);        }        printf("%lld\n",ans);    }    return 0;}




0 0
原创粉丝点击