|
题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1484
/**//* 题意: 给出0 ~ N-1 (1 <= N <= 5000) 的一个排列, 经过循环移位,一共有N个排列, 问这N个排列中逆序对数目最小的。
解法: 树状数组
思路: 树状数组求逆序数有一个经典算法,把数字大小对应到树状数组的小标,然后 从后往前遍历每个数字,统计比这个数字小的数的个数,然后将这个数插入到树状 数组中,遍历完毕,累加和就是该序列的逆序对的数目。 这题要求序列循环移位n次,每次移位统计一次逆序对,最后得到最小的,如果 按照前面的算法,最好的情况是O(n^2log(n)),所以需要找到一些技巧,将复杂度 降下来,我们发现以下两个数列: 1. a1, a2, , an-1, an 2. a2, a3, , an, a1 第二个是第一个循环左移一位的结果,如果将第一个序列的逆序数分情况讨论就是 S = A + B;其中A = (a2~an)本身的逆序对数目;B = a1和(a2~an)的逆序对数目; 而第二个序列中则是S' = A + B';其中B' = (n-1) - B,于是S和A如果已知,那么 就可以轻松求得S' = A + (n-1) - (S - A)。这样一来,只要知道前一个序列的逆 序数,下一个序列就可以在O(1)的时间内求得。只要每次更新S 和 A 的值即可。 更加一般化的,S表示当前序列的逆序数对数,A表示比当前数小的数的个数, 题目中数列是一个排列,所以A的值其实就是当前值减一。 */
#include <iostream> #include <cstdio> #include <cstring> using namespace std;
#define maxn 5001
int n; short c[maxn], val[maxn];
int lowbit(int x) { return x & (-x); }
void add(int pos) { while(pos <= n) { c[pos] ++; pos += lowbit(pos); } }
int sum(int pos) { int s = 0; while(pos > 0) { s += c[pos]; pos -= lowbit(pos); } return s; }
int main() { int i; while(scanf("%d", &n) != EOF) { for(i = 1; i <= n; i++) { int x; scanf("%d", &x); val[i] = x + 1; } for(i = 1; i <= n; i++) c[i] = 0; int ans = 0; for(i = n; i >= 1; i--) { int x = sum(val[i] - 1); add(val[i]); ans += x; }
int Min = ans; int A = val[1] - 1; for(i = 2; i <= n; i++) { ans = ans - A + (n-1-A); A = val[i] - 1; if(ans < Min) Min = ans; } printf("%d\n", Min); } return 0; }
|