首页 > 代码库 > HDU1394_Minimum Inversion Number(线段树/逆序数)

HDU1394_Minimum Inversion Number(线段树/逆序数)

解题报告

题目传送门

题意:

给n个数,每次左移一位,求最小逆序数。

思路:

如果每次左移一位求一次逆序数肯定不行的。

可以知道,每次左移一位,也就是第一个数移到最后一位,逆序数应该减去第一个数以后比第一个数小的个数,再加上比第一个数大的个数。

原本用线段树求出每一位后面比这一位小的个数再用上面的办法求最小逆序数,没有想到每一次移动会导致后面比它本身大的数都要加1。

这题巧妙就在这n个数都在0-n里面,且没有重复。

所以第一个数以后比第一个数小的个数就是第一个数的数值。比它大就是n-1-第一个数。

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;
int sum[200000],num[5100],a[5100];
void push_up(int rt,int l,int r) {
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void update(int rt,int l,int r,int p,int v) {
    if(l==r) {
        sum[rt]=v;
        return ;
    } else {
        int mid=(l+r)>>1;
        if(p<=mid)update(rt<<1,l,mid,p,v);
        else update(rt<<1|1,mid+1,r,p,v);
    }
    push_up(rt,l,r);
}
int q_sum(int rt,int l,int r,int ql,int qr) {
    if(ql>r||qr<l)return 0;
    if(ql<=l&&r<=qr)return sum[rt];
    int mid=(l+r)>>1;
    return q_sum(rt<<1,l,mid,ql,qr)+q_sum(rt<<1|1,mid+1,r,ql,qr);
}
int main() {
    int n,i,j;
    while(~scanf("%d",&n)) {
        memset(sum,0,sizeof(sum));
        memset(num,0,sizeof(num));
        for(i=0; i<n; i++)
            scanf("%d",&num[i]);
        int ans=0,minn;
        for(i=0; i<n; i++) {
            a[i]=q_sum(1,0,n-1,num[i],n-1);
            ans+=a[i];
            update(1,0,n-1,num[i],1);
        }
        minn=ans;
        for(i=0; i<n; i++) {
            if(ans-num[i]+n-num[i]-1<minn)
                minn=ans-num[i]+n-num[i]-1;
            ans=ans-num[i]+n-num[i]-1;
        }
        printf("%d\n",minn);
    }
    return 0;
}