HDU 5592 ZYB's Premutation(树状数组+二分)
题意:给一个排列的每个前缀区间的逆序对数,让还原 原序列。
思路:考虑逆序对的意思,对于k = f[i] - f[i -1],就表示在第i个位置前面有k个比当前位置大的数,那么也就是:除了i后面的数字之外,它是在剩下的数字当中第k+1大的。
知道这个之后,可以用树状数组来帮助找出剩下的数中第k大的数,刚开始我们可以让1~n中每个元素都标记为1,那么他们的前缀和就代表它是第几小。所以,我们可以对于他们的和来二分快速寻找第k大数。其实在树状数组里面是按照第(i-k)小来找的。找完之后要删除这个元素的值。将它标记为0;
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int maxn = 50005; int f[maxn]; int c[maxn]; int ans[maxn]; int lowbit(int x) { return x & (-x); } void add(int pos, int num, int n) { while (pos <= n) { c[pos] += num; pos += lowbit(pos); } } int getSum(int pos) { int sum = 0; while (pos > 0) { sum += c[pos]; pos -= lowbit(pos); } return sum; } void init(int n) { memset(c, 0, sizeof(c)); for (int i = 1; i <= n; i++) add(i, 1, n); } int search(int k, int n) { int l = 1, r = n, mid; while (l <= r) { mid = (l + r) / 2; int sum = getSum(mid); if (sum >= k) r = mid - 1; else l = mid + 1; } return l; } int main() { int T, n; scanf("%d", &T); while (T--) { scanf("%d", &n); init(n); for (int i = 1; i <= n; i++) scanf("%d", &f[i]); for (int i = n; i >= 1; i--) { int k = f[i] - f[i - 1];//第k+1大 k = i - k;//在剩下的里面第i - k 小 int pos = search(k, n);//二分找出第k小的位置 ans[i] = pos; add(pos, -1, n); } for (int i = 1; i < n; i++) printf("%d ", ans[i]); printf("%d\n", ans[n]); } return 0; }