题目描述
HDU 1394 Minimum Inversion Number
解题思路
题目大意:
给出0~n-1的一个排列,每次可以把前m个数放在后面,eg:
a1
,
a2
, …,
an−1
,
an
(m=0)
a2
,
a3
, …,
an
,
a1
(m=1)
…
an
,
a1
,
a2
, …,
an−1
(m=n−1)
问在上述排列中, 逆序数最小为多少?
概念: (
ai
,
aj
) 是逆序对 当且仅当
i<j
时,
ai
>
求逆序数的思路:
1) 按定义: 暴力O(
n2
)循环计算
2) 可以用线段树来查询区间[
ai+1
,
n−1
], 节点维护的信息是在
ai
前面的,且比它大的数的个数. 复杂度优化到O(
nlogn
)
对于本题, 每移动一个元素,逆序数改变的公式为:
sum = sum + n - 2*a[i] - 1;
eg:
5 4 3 6 1 2 7 记当前排列的逆序数为 sum
则 移动一个元素之后得到序列
4 3 6 1 2 7 5
因为5移到最后,则逆序数减少了(5,4),(5,3),(5,2),(5,1) 共四个 即
ai−1
个
同时增加了 (6,5), (7,5) 共两个 即
n−ai
个
所以 sum = sum - (a[i]-1) + (n-a[i]) = sum + n - 2*a[i] - 1;
参考代码
#include <stdio.h>
#define lson rt<<1
#define rson rt<<1|1
#define mid ((l+r) >> 1)
const int MAX_N = 5010;
int sum[MAX_N << 2];
int a[MAX_N];
void pushup(int rt){
sum[rt] = sum[lson] + sum[rson];
}
void build(int l, int r, int rt){
sum[rt] = 0;
if (l == r) return ;
build(l, mid, lson);
build(mid+1, r, rson);
}
void update(int k, int l, int r, int rt){
if (l == r){
sum[rt] = 1;
return ;
}
if (k <= mid) update(k, l, mid, lson);
else update(k, mid+1, r, rson);
pushup(rt);
}
int query(int L, int R, int l, int r, int rt){
if (L <= l && r <= R){
return sum[rt];
}
int ans = 0;
if (L <= mid) ans += query(L, R, l, mid, lson);
if (R > mid) ans += query(L, R, mid+1, r, rson);
return ans;
}
int main(){
int n;
while (~scanf("%d", &n)){
build(0, n-1, 1);
int cnt = 0;
for (int i = 0;i < n;++i){
scanf("%d", a+i);
cnt += query(a[i], n-1, 0, n-1, 1);
update(a[i], 0, n-1, 1);
}
int ans = cnt;
for (int i = 0;i < n;++i){
cnt += n - 2*a[i] - 1;
if (cnt < ans) ans = cnt;
}
printf("%d\n", ans);
}
return 0;
}