HDU_4911_Inversion_归并统计逆序对_杭电多校A题

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4911

http://vjudge.net/contest/view.action?cid=52719#problem/A


1.题意:

一个包含n个数的数列,你可以操作不超过k次,每次可以交换相邻的两个数。问你最后逆序对数目的最小值。

2.题解:

(1)每次交换操作,只能减少一个逆序对,例如 (a>b)(b>c)(c>d)有a,b,c,d这个数列,通过交换(b,c)得到a,c,b,d,那么只是(b,c)这对逆序对消失了,其他的逆序关系不受影响。

(2)如果原数列含有sum个逆序对,那么你通过交换可以得到Max(sum-k,0)个逆序对

(3)统计逆序对的朴素算法是n^2的复杂度,归并法用nlogn的复杂度就可以解决。

(4)网上对于归并法的介绍很多,但是我看到的博客只有一篇看懂了,大多数都没有仔细说,这里我就介绍一遍归并求逆序对的方法。

假如我们要将两个有序(从小到大)数列Array[l,m],Array[m+1,r]合并成一个的有序数列,我们可以这样做。用p指向第一个数组的首位,用q指向第二个数组的首位,用cur指向临时数组的首位,每次把小的那一个插到临时数组的末尾,同时移动指针,这样最后临时数组就是一个有序的了。操作过程中,当A[p]<=A[q]时,t[cur++]=A[p++](A[p]更小,就将A[p]插到t的末尾),当A[p]>A[q]时,由于A是从小到大排列的,A[p,m]>A[q],所以在l~m之间有m-p+1个数可以和A[q]构成逆序对(对于任意位置上的那个数,每次都只会扫到位置在他前面且值比他大的数,而且每次一旦扫到,那个位置在他前面且值比他大的数在临时数组里就会放到他的后面,保证不会重复计算到,接着插入临时数组t[cur++]=A[q++]。

(5)本题还有树状数组的解法,比较容易理解,所以再介绍一遍树状数组的。

首先10^9的范围太大了,所以将数值离散化成10^5的,方法就是先排序,再去除重复的值。原数组从后到前扫描一遍复杂度为n,对于每个数,用树状数组看在他后面有多少个比他小的数的复杂度时logn,这样总的复杂度是nlogn。


归并code:

#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=111111;
int a[MAXN],t[MAXN];
typedef long long LL;
LL ans;

void merge(int l,int mid,int r){
    int p=l,q=mid+1,cur=l;
    for(int i=l;i<=r;i++)
        t[i]=a[i];
    while(p<=mid&&q<=r){
        if(t[p]<=t[q]){
            a[cur++]=t[p++];
        }else{
            a[cur++]=t[q++];
            ans+=mid-p+1;
        }
    }
    while(p<=mid)
        a[cur++]=t[p++];
    while(q<=r)
        a[cur++]=t[q++];
}
void mergesort(int l,int r){
    if(l>=r)return;
    int m=(l+r)>>1;
    mergesort(l,m);
    mergesort(m+1,r);
    merge(l,m,r);
}
int main(){
    int n,k;
    while(scanf("%d%d",&n,&k)!=EOF){
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        ans=0;
        mergesort(1,n);
        printf("%I64d\n",max(0LL,ans-k));
    }
    return 0;
}

树状数组code:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN=111111;
int a[MAXN],t[MAXN],b[MAXN],n,k,m;
int query(int x){
    int s=0;
    while(x){
        s+=a[x];
        x-=x&(-x);
    }
    return s;
}
void add(int x){
    while(x<=n){
        a[x]++;
        x+=x&(-x);
    }
}
int bs(int v){
    int l=1,r=m,mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(v==b[mid]) break;
        if(b[mid]>v) r=mid-1;
        else l=mid+1;
    }
    return mid;
}
int main(){
    while(scanf("%d%d",&n,&k)!=EOF){
        for(int i=1;i<=n;i++){
            scanf("%d",&t[i]);
            b[i]=t[i];
            a[i]=0;
        }
        sort(b+1,b+1+n);
        m=unique(b+1,b+1+n)-(b+1);
        LL ans=0;
        for(int i=n,p;i;i--){
            p=bs(t[i]);
            add(p);
            ans+=query(p-1);
        }
        printf("%I64d\n",max(0LL,ans-k));
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值