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): 14649 Accepted Submission(s): 8943

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

思路:

求出当前排列的逆序数对数,然后把第一个数移到末尾,再求此排列下的逆序数对数。直到排列变成变成最初。

这道题暴力解法是两层for循环进行比较。5000*5000,嗯,虽然不会超时,但是这里更好的方法是用线段树来解答。

那么我们线段树的值是存什么呢?如果存最大值然后再比较,看起来不错,但是在数据起伏比较大的时候基本上会遍历到叶子节点,优势并不大,幸好这题给的数据是从0到n-1,这样我们就可以以他们是否出现建树。

这里我们用一个样例来加以说明:
假如给定的顺序是2 3 8 4 0 5 1 6
首先我们把树初始化为0,那么数就是这个样子。
这里写图片描述
记住,我们树的内容是给定元素出现了几次。
我们记总数为ans=0;
第一次我们先在书中查找比2大的树。因为是空树,所以没有。其实就相当于我们现在只有一个数,求它的逆序对。
所以现在ans=0;

然后我们把2加入树中。
这里写图片描述
记住,我们树里是表示的区间该区间的数出现过的次数。
第二次我们找3的逆序数,我们只需要找到出现过的数中比3大的,因为我们在的数是从前往后加的,只要所以只需要找到i比3小,值比3大的,那么出现过的数因为都在树中可以查询,我们只需要查询比3大的所有数有多少个就行了。
查询完没有比3大的,再把3加入树。
这里写图片描述
现在ans=0;

同理,我们把8加进去。
这里写图片描述
ans=0;

现在我们要放4了,先查找之前有没有出现过比4大的,在树中很快我们就找到有一个,(Find(root,5,8)=1),所以这里ans=1;
现在再把点加入树中。
这里写图片描述

最后我们会看到这样一棵树。
这里写图片描述
即代表0~7的所有数都查找完了。

得到的ans也就是这组数的逆序数了。

当我们把这种状态求出来了之后,还要把第一个数移动到最后,再算一次,这样的即使优化过时间上也要5000*5000*log5000,超时!

细心观察我们会发现,比如上面的例子:2 3 8 4 0 5 1 6
我们把2移动到最后一个位置,后面比2小的逆序数对就没了,但是会后面比2大的都回组成新的逆序数对,所以我们减少了2-0种,也就是a[0]-min(a[])增加了8-2-1种,也就是n-a[0]-1,所以我们最后增加了n-2*a[0]-1种,第二次就是相比第一次增加n-2*a[0]-1种……

int ans=an;
        for(int i=1; i<=n; i++)
        {
            an=an+n-2*a[i]-1;
            ans=min(ans,an);
        }
        printf("%d\n",ans);

所以我们可以用n次操作来求后面的解。

代码:

#include"stdio.h"
#include"stdlib.h"
#include"string.h"
#include"vector"
using namespace std;
const int N = 5005;
struct node
{
    int left;
    int right;
    int sum;
};
node tree[N*4];
int a[N];

int min(int a,int b)
{
    if(a>b) return b;
    else return a;
}

void build(int id,int l,int r)
{
    tree[id].left=l;
    tree[id].right=r;
    tree[id].sum=0;
    if(r==l)    return;
    else
    {
        int mid=(r+l)/2;
        build(id*2,l,mid);
        build(id*2+1,mid+1,r);
    }
}

int Find(int id,int l,int r)
{
    if(tree[id].left>=l&&tree[id].right<=r)
        return tree[id].sum;
    else
    {
        int mid=(tree[id].left+tree[id].right)/2;
        int tans=0;
        if(l<=mid)
            tans=tans+Find(id*2,l,r);
        if(r>mid)
            tans=tans+Find(id*2+1,l,r);
        return tans;
    }
}
//更新是把加入点位置的值+1
void update(int id,int pos)
{
    if(tree[id].left==tree[id].right)
        tree[id].sum++;
    else
    {
        int mid=(tree[id].left+tree[id].right)/2;
        if(pos<=mid)
            update(id*2,pos);
        else
            update(id*2+1,pos);
        tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
    }
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        memset(tree,0,sizeof(tree));
        build(1,0,n-1);
        int an=0;
        for(int i=1; i<=n; i++)
        {
        //先查找,再把点放入树中给下一个加入的点做对比
            scanf("%d",&a[i]);
            an=an+Find(1,a[i]+1,n-1);
            update(1,a[i]);
        }

        int ans=an;
        for(int i=1; i<=n; i++)
        {
            an=an+n-2*a[i]-1;
            ans=min(ans,an);
        }
        printf("%d\n",ans);

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值