逆序对的两种求法

题目链接:
http://codeup.cn/problem.php?cid=100000636&pid=0

题目:
问题 A: 最少的交换
时间限制: 1 Sec 内存限制: 32 MB
题目描述
现在给你一个由n个互不相同的整数组成的序列,现在要求你任意交换相邻的两个数字,使序列成为升序序列,请问最少的交换次数是多少?
输入
输入包含多组测试数据。每组输入第一行是一个正整数n(n<500000),表示序列的长度,当n=0时。
接下来的n行,每行一个整数a[i](0<=a[i]<=999999999),表示序列中第i个元素。
输出
对于每组输入,输出使得所给序列升序的最少交换次数。
样例输入

5
9
1
0
5
4
3
1
2
3
0

样例输出

6
0

思路:
每次只能交换相邻的两个数,实则最少的交换次数就是逆序对次数。逆序对满足: i &lt; j &amp; &amp; a [ i ] &gt; a [ j ] i&lt;j\&amp;\&amp;a[i]&gt;a[j] i<j&&a[i]>a[j]
这里采用归并排序树状数组两种方式来求解。

对于归并排序,因为涉及到a[i]和a[j]两两比较,每次二分完成后。对于局部已经排序好的a[]数组,如果i<j并且此时的a[i]>a[j],说明从i到mid均有a[i]>a[j],所以总的逆序对数目sum+=mid-i+1。注意sum可能超int,应该取long long类型。

对于树状数组,首先采用hash的思想,对于每个出现的数每出现一次就加一,那怎么求逆序对呢?利用getsum(x)求出x前面(包括x)已经出现的值的次数,再利用已经插入的个数减去getsum(x)即可。由于a[i]的最大值已经超过了int,这里首先离散化,即x表示的并不是x本身,而是x出现的索引。
归并排序求逆序对 code:

#include <iostream>
#include<cstdio> 
#include<cstring>
#define ll long long
using namespace std;
ll a[500005],d[500005];
ll sum=0;
void msort(int l,int r){
    if(l>=r) return;
    int mid=(l+r)>>1;
    msort(l,mid);msort(mid+1,r);
    int i=l,j=mid+1,t=l;
    while(i<=mid&&j<=r){
        if(a[i]<=a[j]){
            d[t++]=a[i++];
        }else{
            d[t++]=a[j++];
            sum+=mid-i+1;//统计逆序对数目,注意sum应为long long类型
        }
    }
    while(i<=mid) d[t++]=a[i++];
    while(j<=r) d[t++]=a[j++];
    for(int k=l;k<=r;k++) a[k]=d[k];
}
int main(int argc, char** argv) {
    int n;
    while(scanf("%d",&n)!=EOF&&n){
        memset(d,0,sizeof(d));
        sum=0;
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
         
        msort(1,n);
        printf("%lld\n",sum);
    }
    return 0;
}

树状数组求逆序对 code:

#include <iostream>
#include<cstdio> 
#include<cstring>
#include<algorithm>
#define ll long long
#define lowbit(x) (x&(-x))
using namespace std;
struct node{
    ll v;
    int ind;
    bool operator <(const node &b) const{
        return v<b.v;//
    }
}num[500005]; 
ll C[500005],A[500005];
void update(int x,int v){
    for(int i=x;i<500005;i+=lowbit(i)){
        C[i]+=v;
    }
}
ll getsum(int x){
    ll sum=0;
    for(int i=x;i>0;i-=lowbit(i)){
        sum+=C[i];
    }
    return sum;
}
int main(int argc, char** argv) {
    int n;
    while(scanf("%d",&n)!=EOF&&n){
        memset(C,0,sizeof(C));
        memset(num,0,sizeof(num));
        memset(A,0,sizeof(A));
         
        for(int i=1;i<=n;i++){
            scanf("%lld",&num[i].v);
            num[i].ind=i;
        }
        sort(num+1,num+n+1);
        for(int i=1;i<=n;i++){//离散化
            if(i==1||num[i].v!=num[i-1].v) A[num[i].ind]=i;
            else if(num[i].v==num[i-1].v) A[num[i].ind]=A[num[i-1].ind]; 
        }
        ll sum=0;
        for(int i=1;i<=n;i++){
            update(A[i],1);//A[i] int范围内 
            sum+=i-getsum(A[i]);
        }
         
        printf("%lld\n",sum);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值