HDOJ 1394 - Minimum Inversion Number

本文介绍了一种使用线段树求解一系列由循环移位产生的数列的逆序数最小值的问题。通过分析数列特性,利用线段树高效计算逆序数变化,最终求得所有可能数列中的逆序数最小值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Advanced Data Structures :: Segment Tree


Description

给你一个长度为n的数列,你每次都可以把第一个元素放到最后一位,成为一个新的数列。

这样不停循环,你就可以得到n种不同的数列。
我们知道,对于每个数列都有一个逆序数。
输入初始的数列,输出这n种不同数列的逆序数的最小值。
注意数列的元素是从0到n - 1。


Type

Advanced Data Structures :: Segment Tree


Analysis

对于这道题,因为数列的元素是从0到n – 1,所以我们根据元素的值,就可以知道,这个元素An在数列中是第几大的,比它小的有An个,比它大的有n – An – 1个。
我们可以先计算出初始数列的逆序数,然后去计算把第一个元素移到最后,逆序数数值的变化。
可以想到,把第一个数移到最后,逆序数会增加n – An(因为比它大的元素),然后减少An个(因为比它小的元素),最后逆序数数值的变化其实就是增加n – 2An – 1。
这样,我们就可以在知道初始数列逆序数的情况下,用O(n)的时间算出所有逆序数并求最大值。
剩下要做的就是,如何去初始数列求逆序数。


求逆序数的方法有两种,一种是排序,计算移动元素的次数(仅限于移动邻位)。
另外一种,可以对每一个元素,计算之前有多少比它大的元素,也就是去找出所有逆序数对。
第一种方法的时间复杂度和排序的时间复杂度相同,只要在移动元素的时候count一下就可以,常用的是归并排序,时间复杂度O(n lg n)。
而第二种方法,简单去暴力枚举,时间复杂度是O(n^2),但是也有O(n lg n)的方法,就是线段树。
线段树的叶子结点初始化为0,表示该元素还未插入,插入后则赋值为1。
从前到后地去枚举并插入每个元素。
枚举时利用线段树计算比它大的元素有几个已经插入,则可以知道在它前面有多少个比它大的元素。
枚举和计算之后,将这个数插入到线段树中。
这样便可以利用线段树,达到O(n lg n)的时间复杂度,以下代码用的就是这种方法。


Solution

// HDOJ 1394
// Minimum Inversion Number
// by A Code Rabbit

#include <cstdio>
#include <cstring>

#define LSon(x) (x << 1) 
#define RSon(x) (x << 1 | 1) 

const int MAXN = 5002;
const int ROOT = 1;

struct Seg{
    int w;
};

struct SegTree {
    Seg node[MAXN << 2];
    void Update(int pos) { node[pos].w = node[LSon(pos)].w + node[RSon(pos)].w; }
    void Build() { memset(node, 0, sizeof(node)); }
    void Modify(int l, int r, int pos, int x) {
        if (l == r) { node[pos].w = 1; return; }
        int m = l + r >> 1;
        if (x <= m) Modify(l, m, LSon(pos), x);
        else Modify(m + 1, r, RSon(pos), x);
        Update(pos);
    }
    int Query(int l, int r, int pos, int x, int y) {
        if (x <= l && r <= y) return node[pos].w;
        int m = l + r >> 1;
        int res = 0;
        if (x <= m) res += Query(l, m, LSon(pos), x, y);
        if (y > m) res += Query(m + 1, r, RSon(pos), x, y);
        return res;
    }
};

int n;
int a[MAXN];

SegTree tree;

int main() {
    while (scanf("%d", &n) != EOF) {
        tree.Build();
        int sum = 0;
        for (int i = 0; i < n; ++i) {
            scanf("%d", &a[i]);
            sum += tree.Query(0, n - 1, ROOT, a[i], n - 1);          
            // Query(0, n - 1, 1, a[i] + 1, n - 1) is wrong, because a[i] + 1 is
            // maybe more than n - 1.
            tree.Modify(0, n - 1, ROOT, a[i]);
        }
        int min = sum;
        for (int i = 0; i < n - 1; ++i) {
            sum += n - a[i] - 1;
            sum -= a[i];
            min = sum < min ? sum : min;
        }
        printf("%d\n", min);
    }

    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值