HDOJ 1394 - Minimum Inversion Number

来源:互联网 发布:网络信息安全学报 编辑:程序博客网 时间:2024/06/07 07:21

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;}