题意分析:
实质上来说,这道题就是求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; }