题目链接:
[HDU 1394]Minimum Inversion Number[逆序对][线段树]
题意分析:
实质上来说,这道题就是求n个小于n的数的排列变成环之后,从其中某一点切开的最小逆序对数。
解题思路:
第一步:求出初始逆序对数。可以用归并排序,这里我用了线段树。
能使用线段树的理由:线段树存储的是一个区间段的值,应用到求逆序对数,我们就可以让它初始时都为0,每次加入一个数x前,查询区间[x,n - 1]的和,即:之前有多少个数大于x,加入到答案中。然后更新x这个位置的值,表示x这个位置现在有数了。
第二步:循环计算新段的逆序对数。这里考虑将排头元素移到末尾后,和原排列想比,增加了n - 1 - a[i]个逆序对,减少了a[i]个逆序对,因为所有除了a[i]的元素都在它前面了。这样就可以O(n)求出所有中的最小值。
总的复杂度nlogn。
然后把这题扩展到一般情况,任意n个不相同的数,怎么做呢?
道理还是一样的,可以把他们排个序,重新从0开始编号,于是题目又变成了本题~~~\(^o^)/
个人感受:
一道题弄了好久。进度实在慢成蜗牛啊= =。
具体代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#define lowbit(x) (x & (-x))
#define root 0, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
using namespace std;
const int MAXN = 5e3 + 111;
int sum[MAXN << 2], a[MAXN];
void push_up(int rt)
{
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
int query(int L, int R, int l, int r, int rt)
{
if (L <= l && r <= R)
{
return sum[rt];
}
int ret = 0;
int m = (l + r) >> 1;
if (L <= m) ret += query(L, R, lson);
if (m < R) ret += query(L, R, rson);
return ret;
}
void update(int x, int l, int r, int rt)
{
if (l == r)
{
++sum[rt];
return;
}
int m = (l + r) >> 1;
if (x <= m) update(x, lson);
else update(x, rson);
push_up(rt);
}
int main()
{
int n;
while (~scanf("%d", &n))
{
memset(sum, 0, sizeof sum);
int ans = 0;
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
ans += query(a[i], n - 1, root);
update(a[i], root);
}
int temp = ans;
for (int i = 1; i < n; ++i)
{
temp += (n - 1 - a[i]) - a[i];
ans = min(ans, temp);
}
printf("%d\n", ans);
}
return 0;
}