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;
}

 

posted @ 2015-12-10 16:16  Howe_Young  阅读(626)  评论(0编辑  收藏  举报