本题有两个考点:
1 求逆序数的性质
计算逆序数的公式, 一个数arr[i]从前面放到后面,必然会有n-arr[i]-1个数比这个大,那么就有n-arr[i]-1个逆序数增加,同时因为前面少了个arr[i]数,那么就必然有arr[i]个(加上零)数比起小的数失去一个逆序数,总共失去arr[i]个逆序数,所以新的逆序数为增加了n-arr[i]-1-arr[i]个逆序数(当然有可能是减小了,视arr[i]的值而定。
2 如何求一个数列的逆序数
可以使用归并排序来求,也可以使用线段树来求。两者都是二分法的思想,故此时间效率是O(nlgn)
使用线段树来求逆序数是有点难度的,参考了神牛的代码,恍然大悟。
虽然说本题线段树应该不如使用归并排序那么好,但是确实灵活运用线段树的极好例子。
这些数据结构就犹如神兵利器,让我们有可能战胜比自己更加强大的敌人。
#pragma once
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
class MinimumInversionNumber_2
{
static const int SIZE = 5001;
int *segTree;
inline int lChild(int r) { return r<<1; }
inline int rChild(int r) { return r<<1|1; }
void pushUp(int rt)
{
segTree[rt] = segTree[lChild(rt)] + segTree[rChild(rt)];
}
void build(int l, int r, int rt)
{
segTree[rt] = 0;
if (l == r) return ;
int m = l + ((r-l)>>1);
build(l, m, lChild(rt));
build(m+1, r, rChild(rt));
}
void update(int p, int l, int r, int rt)
{
if (l == r)
{
segTree[rt]++;
return;
}
int m = l + ((r-l)>>1);
if (p <= m) update(p, l, m, lChild(rt));
else update(p, m+1, r, rChild(rt));
pushUp(rt);
}
int query(const int L, const int R, int l, int r, int rt)
{
if (L <= l && r <= R)
{
return segTree[rt];
}
int m = l + ((r-l)>>1);
int res = 0;
if (L <= m) res += query(L, R, l, m, lChild(rt));
if (R > m) res += query(L, R, m+1, r, rChild(rt));
return res;
}
public:
MinimumInversionNumber_2() : segTree((int *) malloc(sizeof(int) * (SIZE<<2)))
{
int n;
int arr[SIZE];
while (~scanf("%d", &n))
{
build(0, n-1, 1);
int sum = 0;
for (int i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
sum += query(arr[i], n-1, 0, n-1, 1);
update(arr[i], 0, n-1, 1);
}
int ans = sum;
for (int i = 0; i < n; i++)
{
/*计算逆序数的公式, 一个数arr[i]从前面放到后面,必然会有n-arr[i]-1个数比这个大,那么就有n-arr[i]-1个逆序数增加,同时因为前面少了个arr[i]数,那么就必然有arr[i]个(加上零)数比起小的数失去一个逆序数,总共失去arr[i]个逆序数,所以新的逆序数为增加了n-arr[i]-1-arr[i]个逆序数*/
sum += n - arr[i] - 1 - arr[i];
ans = min(ans, sum);
}
printf("%d\n", ans);
}
}
~MinimumInversionNumber_2()
{
free(segTree);
}
};