HDU 1394 Minimum Inversion Number【线段树求逆序数】

HDU 1394 Minimum Inversion Number【线段树求逆序数】http://acm.hdu.edu.cn/showproblem.php?pid=1394

Minimum Inversion Number

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 11014    Accepted Submission(s): 6772


Problem Description
The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy i < j and ai > aj.

For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:

a1, a2, ..., an-1, an (where m = 0 - the initial seqence)
a2, a3, ..., an, a1 (where m = 1)
a3, a4, ..., an, a1, a2 (where m = 2)
...
an, a1, a2, ..., an-1 (where m = n-1)

You are asked to write a program to find the minimum inversion number out of the above sequences.
 

Input
The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1.
 

Output
For each case, output the minimum inversion number on a single line.
 

Sample Input
  
  
10 1 3 6 9 0 8 5 7 4 2
 

Sample Output
  
  
16
 

Author
CHEN, Gaoli
 

Source
 

【题意】有n个数是0~n-1的一个排列,每次把第一个数放到列末尾,找出其最小的逆序数和。

【分析主要是利用线段树求逆序数,建的是一棵空树,然后每插入一个点之前,统计大于这个数的有多少个,直到所有的数都插入完成,就结束了逆序树的统计。

要得出答案主要是利用了一个结论,如果是0到n的排列,那么如果把第一个数放到最后,对于这个数列,逆序数是减少y[i],而增加n-1-y[i]的。(可以这样想,因为是第一个数,所有的数都在它后面,那么在当前位置pos比它大的数也在它后面,那么第一个数调到后面之后,在pos不成立的逆序数就成立了,所以多了n-y[i]-1,但是也少了在pos成立的逆序数,即y[i]个)

【归并排序求逆序数代码】
//Accepted	1394	15MS	316K	1441B	C++
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 5000 + 20;
long long n;
long long a[maxn], c[maxn], num[maxn], cnt;

void mergeSort(int l, int r)//求a[]序列的逆序数cnt模板:mergeSort(0, n);
{
    long long mid, i, j, tmp;
    if(r >l+1){
        mid = (l+r)/2;
        mergeSort(l, mid);
        mergeSort(mid, r);
        tmp = l;
        for(i=l, j=mid; i<mid && j<r; ){
            if(a[i]>a[j]){
                c[tmp++] = a[j++];
                cnt += mid-i;//总的逆序数
            }
            else c[tmp++] = a[i++];
        }
        if(j<r) for(; j<r; j++) c[tmp++] = a[j];
        else for(; i<mid; i++) c[tmp++] = a[i];
        for(i=l; i<r; i++) a[i] = c[i];
    }
}

int main()
{
    while(~scanf("%d", &n))
    {
        long long mmin = 0xfffffff;
        for(int i=0; i<n; i++){
            scanf("%d", &a[i]);
            num[i] = a[i];
        }
        cnt = 0;
        mergeSort(0, n);
        mmin = min(cnt, mmin);
        long long t = cnt;
        for(int i=0; i<n; i++)
        {
//            int j;
//            for(j=i; j<n; j++) a[j-i] = num[j];
//            for(int k=j-i, cc=0; k<n; k++, cc++) a[k] = num[cc];
//            cnt = 0;
//            mergeSort(0, n);

            /*
            求最小逆序数的时候有个巧妙的想法,
            当把x放入数组的后面,此时的逆序数
            应该为x没放入最后面之前的逆序总数
            加上(n-x)再减去(x-1);
                sum = sum+(n-x[i])-(x[i]-1)。
            */

            t = t-num[i]+n-num[i]-1;
            mmin = min(t, mmin);
        }
        printf("%d\n", mmin);
    }
    return 0;
}



【线段树求逆序数代码】

//线段树求逆序数
//Accepted	1394	31MS	316K	2032 B	C++
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

const int maxn = 5000+10;
struct NODE
{
    int w;
    int fmax, fmin, rmax, rmin;
    //分别代表序列的前方比w大的个数,小的个数,
    //后方比w大的个数,小的个数
}node[maxn];
int tree[maxn*4], ffmax, ffmin;

void build(int l, int r, int k)
{
    int m = (l+r)/2;
    tree[k] = 0;
    if(l==r) return ;
    build(l, m, k*2);
    build(m+1, r, k*2+1);
}

void updata(int l, int r, int k, int w)
{
    int m = (l+r)/2;
    tree[k]++;
    if(l==r) return ;
    if(w <= m){//往左走
        ffmax += tree[k*2+1];//记录比w大的个数
        updata(l, m, k*2, w);
    }
    else{//往右走
        ffmin += tree[k*2];//记录比w小的个数
        updata(m+1, r, k*2+1, w);
    }
}

int main()
{
    int n, mmin, mm;
    while(~scanf("%d", &n))
    {
        build(1, n, 1);
        mm = 0;
        for(int i=1; i<=n; i++)
        {
            scanf("%d", &node[i].w);
            node[i].w++;//变成1~n
            ffmax = 0; ffmin = 0;
            updata(1, n, 1, node[i].w);
            node[i].fmax = ffmax;//前面输入的比w大的个数
            node[i].fmin = ffmin;//前面输入的比w小的个数
            node[i].rmax = (n-node[i].w)-ffmax;//根据前面的可推出后面比w大的个数
            node[i].rmin = (node[i].w-1)-ffmin;//根据前面的可推出后面比w小的个数
            mm += ffmax;//记录输入序列的总逆序数
        }
        mmin = mm;//记录n个(循环)序列的最小的总逆序数
        for(int i=1; i<=n; i++)//把第i个数放到序列的最后
        {
            mm = mm-node[i].rmin;//移动后,对后方小于w(i)的逆序数的影响
            mm = mm-node[i].fmin;//移动后,对己经循环的前方数(小于w(i))的逆序数的影响
            mm = mm+node[i].fmax;//移动后,对本身(小于己经循环的前方数的逆序数)的影响
            mm = mm+node[i].rmax;//移动后,对本身(小于还没循环数的逆序数)的影响
            mmin = min(mmin, mm);
        }
        printf("%d\n", mmin);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值