求逆序对常用方法是归并排序,当然线段树也可以解决,但是空间复杂度就会更大。
这题两者都可用。因为只需要第一次求出逆序对数即可,后面的排列都可以推出来。
具体怎么推?
假设 sum 是第一次求出来的逆序对。 第一次排列是 1 3 6 9 0 8 5 7 4 2
那么,第二次就会是 3 6 9 0 8 5 7 4 2 1
1 被放到末尾,自然需要在 sum 上减去包括1的逆序对了。观察一下,1 是在最左边的,在1还在首部时,右边能跟1组成逆序对的就只有0,所以 sum 需要先减掉 1.
第三次 6 9 0 8 5 7 4 2 1 3 。同上, sum 要减掉 2.
回头看 第二次 ,1 被放在尾部后,可能会有新的逆序对产生,所以能跟尾部1组成逆序对的,没有, 所以是 sum + 0。
但是在第三次时候,前面总共有 0, 1, 2 三个可以跟尾部3组成逆序对。 所以这里就需要 sum + 3。
后面同样规律。
代码:
#include <cstdio>
#define MAXN (5000 + 10)
#define lson rt << 1
#define rson rt << 1 | 1
int ns[MAXN << 2];
int nums[MAXN];
int n, input, sum, ans;
void build(int rt, int l, int r)
{
if (l == r) ns[rt] = 0;
else {
int mid = (l + r) / 2;
build(lson, l, mid);
build(rson, mid+1, r);
ns[rt] = ns[lson] + ns[rson];
}
}
int query(int rt, int l, int r, int ll, int rr)
{
if (l == ll && rr == r) return ns[rt];
else {
int mid = (l + r) / 2;
if (rr <= mid) return query(lson, l, mid, ll, rr);
else if (mid < ll) return query(rson, mid+1, r, ll, rr);
else {
return query(lson, l, mid, ll, mid) + query(rson, mid+1, r, mid+1, rr);
}
}
}
void update(int rt, int l, int r, int id)
{
if (l == id && r == id) ns[rt] = 1;
else {
int mid = (l + r) / 2;
if (id <= mid) update(lson, l, mid, id);
else update(rson, mid+1, r, id);
ns[rt] = ns[lson] + ns[rson];
}
}
int main()
{
//freopen("testdata/1394.txt", "r", stdin);
while (scanf("%d", &n) != EOF) {
build(1, 0, n);
sum = 0;
for (int i = 0; i < n; ++i) {
scanf("%d", &nums[i]);
sum += query(1, 0, n, nums[i]+1, n);
update(1, 0, n, nums[i]);
}
ans = sum;
for (int i = 0; i < n-1; ++i) {
//sum -= query(1, 0, n, 0, nums[i]-1); //这种方式同样是先减后加
//sum += query(1, 0, n, nums[i]+1, n); //不过需要注意nums[i]-1会等于-1.这时通过对nums[i]+1进行处理,而 n 就需要+1.
sum = sum - nums[i] + (n - nums[i] - 1);
if (ans > sum) ans = sum;
}
printf("%d\n", ans);
}
}