hdu 1394 Minimum Inversion Number(最小逆序数) 线段树/暴力

传送门:hdu 1394 Minimum Inversion Number

题目大意

给定n个数,求这个数组最小逆序数。
可能有很多人对逆序数不太了解,在这里科普一下:

对于n个元素来讲,他们之间遵循一个标准次序(比如说从小到大排列下去),于是在这个n个元素的任意排列中,当某两个元素不符合这个标准次序的时候,就说这事一个逆序。一个排列中的所有所有逆序之和叫做这个排列的逆序数。

这个题目当中,元素的标准次序是按照下标,从0到n-1的顺序。每一次排列的顺序是把第一个元素移动到最后一个,这样就n中组合(算着初始状态)。这样就有了n个逆序数,我们输出的是这n个逆序数中最小的一个。
比如说样例给的数据:
初始状态(m = 0)1 3 6 9 0 8 5 7 4 2 满足i

解题思路

这个题目有一个坑就是如果不知道逆序数是什么话,很容易就看不懂题目。

逆序数的计算方法:逐个读取这n个元素,每次读取到第i个的时候查询前面大于a[i]的数量,更新sum(sum就代表前i个元素的中大于a[i]的数量),最后得到的sum就是这个数列的逆序数。
接下来我们要找到第二个状态的逆序数,也就是把第一个元素(a[0])移动到最后,这么一来现在的数列和前一个状态的数列来比,从第一个元素到倒数第二个元素的逆序数不变(这个时候前一个状态的数列的第二个已经变为现在状态的第一个了),变得之后只有最后一个元素的的逆序发生了变化,之前比最后一个数(上一个元素的a[0])大的元素都变为了现在状态的逆序,我们设这个增加的逆序为x,所以x = n-1-a[0];以前和a[0]是逆序的元素现在不是逆序了,我们设这个减少的逆序为y,所以y=n-1-x。所以从前一个状态的逆序数推断出这个状态的逆序数为sum = sum+x-y=sum-n-1+2*a[0];
知道了上面的推理我们现在知道就要求出原始序列的逆序数就可以了。我用到了两种方法,一种是线段树一种是暴力。归并排序貌似也是可以,因为逆序数就是通过归并算出来了。
暴力的方法就不用多说了,两个循环。
下面主要说说线段树的左右,首先建树的时候是按照0~n-1建树。不是从1开始的,每当输出的一个元素的时候通过queryTree函数查询书在已经输入的元素中,比这个元素大的数量。(因为线段树是按照顺序的方式建立的,所以查询的时候是查询的是从a[i]到n-1这个区间的),每次输入的时候还有使用的到updateTree()函数把这个元素插入到这个线段树当中。
然后当上面的过程结束之后,也就是求出了原始序列的逆序数,就可以通过一个循环求出最小逆序数。

AC代码

暴力方法

#include<cstdio>

int main()
{
    int n;
    int a[50000];
    while(scanf("%d",&n)!=EOF)
    {
        int sum = 0;
        for(int i=0;i<n;i++)
            scanf("%d",&a[i]);
        for(int i=0;i<n-1;i++)
            for(int j=i+1;j<n;j++)
            if(a[i]>a[j])
                sum++;
        int ans = sum;
        for(int i=0;i<n;i++)
        {
            sum = sum - 2*a[i]+n-1;
            if(ans>sum)
                ans = sum;
        }
        printf("%d\n",ans);
    }
    return 0;
}

线段树

#include<cstdio>
#include<cstring>
#include<set>
#include<stack>
#include<cmath>
#include<string>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<iostream>
#include<map>
using namespace std;
const int MAXN = 200005;
#define lson left,mid,rt<<1
#define rson mid+1,right,rt<<1|1
typedef long long LL;  //lld

struct Node{
    int left;
    int right;
    int sum;
    int mid()
    {
        return (left+right)>>1;
    }
}tree[MAXN*4];

void buildTree(int left,int right,int rt)
{
    tree[rt].left = left;
    tree[rt].right = right;
    tree[rt].sum = 0;
    if(left == right)
        return ;

    int mid = tree[rt].mid();
    buildTree(lson);
    buildTree(rson);
}

int queryTree(int left,int right,int rt,int L,int R)
{
    if(left == L && right == R)
    {
        return tree[rt].sum;
    }

    int mid = tree[rt].mid();
    if(R<=mid)
        return queryTree(lson,L,R);
    else if(L>mid)
        return queryTree(rson,L,R);
    return queryTree(lson,L,mid) + queryTree(rson,mid+1,R);
}

void updateTree(int pos,int left,int right,int rt)
{
    if(left == right)
    {
        tree[rt].sum = 1;
        return ;
    }
    int mid = tree[rt].mid();
    if(pos<=mid)
        updateTree(pos,lson);
    else
        updateTree(pos,rson);
    tree[rt].sum = tree[rt<<1].sum + tree[rt<<1|1].sum;
}

int main()
{
    int n;
    int a[MAXN];
    while(scanf("%d",&n)!=EOF)
    {
        int sum = 0;
        buildTree(0,n-1,1);
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            //在a[i]到n-1区间查询比a[i]大的数,如果前面插入的数值有在这个区间里面的
            //就可以查询出来了,如果没有查询出的是0,
            //因为在建树的时候已经初始化为0
            sum += queryTree(0,n-1,1,a[i],n-1);
            //插入a[i]这个元素
            updateTree(a[i],0,n-1,1);
        }
       // printf("[%d]\n",sum);
        int ans = sum;
        for(int i=0;i<n;i++)
        {
            sum = sum - 2*a[i]+n-1;
            if(ans>sum)
                ans = sum;
        }
        printf("%d\n",ans);
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值