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