【HDU 1394】 Minimum Inversion Number 树状数组 逆序对 详解

本文介绍了一种使用树状数组求解逆序对数量的算法,并通过一个具体问题实例,即寻找序列中逆序对数量最小的操作方式,详细阐述了算法的实现过程和优化思路。

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

题意:给定一个n个元素序列,有n-1次操作,每次可以将头一个元素丢到最后面,问哪一种可以使得操作后序列逆序对最少(初始状态算一种)

思路(树状数组求逆序对):

1.首先我们要知道什么“逆序对”, 逆序对的定义是同时满足a[i]>a[j] && i < j。注意是任意一对序列即可,不是说是单调递减序列。比如1 2 4 3 5 的逆序对就为1,在3这个位置产生的(前面有个4比他大)。换句话说就是看前面有几个比当前数大的,然后每个位置求和就是逆序对数

2.那拿到一个序列,我们怎么求逆序对数量呢?直接暴力的方法我们就直接pass了,也就是说要借助更高效的算法。要想,我们的目的是求“当前这个元素之前有多少个比当前元素大的元素”,那么当前位置的逆序对数量只和我前面加进来的数有关。那好,怎么会想到树状数组来解决这个问题呢?

嘿嘿,想想这样的动态扫描过程,当前元素是X —> 要找前面比X大的---->转化成先找前面比X小的,然后用总数-比X小的就是比X大的---->也就是求【0,X-1】内哪些数在前面的序列里出现过---->对了,可以先对出现过的位置标记一下,出现过的元素贡献就是1嘛(出现1次,当然也只会出现1次)—>那就相当于求和【0,X-1】区间内的离散值了!!

3.当然,文字描述可能比较生硬,我来个图解。
在这里插入图片描述
在这里插入图片描述
像如图的序列1 4 2 3 5 6,一开始离散化数组初始化为0

  1. 1进来,发现当前小于等于1的有1一个, 离散序列里总共数量是1,所以比他大的是1-1=0个
  2. 4进来,发现小于等于4的有两个,总和个数2个,结果是2-2=0 。
  3. 紧接着,2进来,发现小于等于2的有2个,而总和有3个,那么结果3-2=1个(也就是(4,2)组合)
  4. 以此类推,如图。。。。

这样,我们就可以通过树状数组求离散数组的求缀和来求的每个位置的逆序对了,序列的逆序对就是求和的事情。
反过来看题目的表意,有m-1种操作,每次往尾部丢一个,仔细观察,这样的更新是有规律的,我把一个X丢到后面,那么情况是怎么样的?本来你比你大的数有些在后面,这下好了,你跑到最后,那前面所有比你大的数都在你面前,全可以变成逆序对了,这时候,X丢到最后的正贡献就是n-a[i](就是比a[i]大的个数),当然,原来位置上后面本来很多比X小的(在他们的角度上看,X就是前面比自身大,是一个逆序对),这下全没了。这就是负贡献,那结果就是原序列的逆序对更新sum += 正贡献 - 负贡献。枚举每个位置即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <queue>
#include <limits.h>
#define maxn 5000+500
using namespace std;

int b[maxn];
int a[maxn];
int n,m;
int lowbit(int x)
{
        return (x&-x);
}

void add(int pos)
{
        for(int i=pos;i<=n;i+=lowbit(i))
        b[i] ++;
}

int Query(int pos)
{
        int sum = 0;
        for(int i=pos;i>=1;i-=lowbit(i))
        sum += b[i];
        return sum;
}

void init()
{
        memset(b,0,sizeof(b));
        memset(a,0,sizeof(a));
}

int main()
{
    while(cin>>n)
    {
            init();
            int sum =  0;
            for(int i=1;i<=n;i++)
            {
                    scanf("%d",&a[i]);   add(a[i]+1);
                    sum += Query(n) - Query(a[i]+1);
            }
            int minone = sum;
            for(int i=1;i<=n;i++)
            {
                    sum += (n-1-a[i] - a[i] );
                    minone = min(minone,sum);
            }
            cout<<minone<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值