BZOJ 4552 排序(二分 || 线段树合并)

题意:对一个1~n的全排列, 进行m次局部排序
(0, l, r)将区间[l, r]数字升序排列
(1, l, r)将区间[l, r]数字降序排列

仅一次询问, 询问m次局部排序后第q位置上的数字

1 ≤ n, m, q ≤ 1e5


思路:

这题有个神奇的做法,我们可以直接二分答案x,然后把大于等于x的都看作1,比x小的都看作0插入线段树中,

然后执行m个询问,因为数组中要么是0,要么是1,所以区间排序就简单多了,只要求出区间1的个数,然后把这些1如

果升序放到后面,如果降序放到前面。执行完操作后看下q位置是0还是1即可,如果是1就说明比x大,反之比x小。

时间复杂度O(m log^2(n))


当然还有个麻烦点的做法,但是可以支持在线多次询问。(还没实现)

初始时建立n棵只包含一个点的线段树 (动态开点, 范围均为1~n)
• 用线段树的合并完成排序
• 对于每次局部 ([a, b]) 排序
• 从若干线段树 (将每棵线段树代表的区间插入set, 方便每次局部排序快速的找出第一棵包含[a, b]的线段树)中分裂出在[a, b]中的点, 相当于分裂出线段树中连续的一段
• 将分裂出的若干子线段树合并• 查询相当于查询某个线段树中的第k个值
• 时间复杂度O(n log n) 
• 并且支持在线多个询问及局部排序

代码:

#include
#include
#include
#include
using namespace std;
const int maxn = 1e5+5;
const int INF = 0x3f3f3f3f;
int tree[maxn*4], set[maxn*4], a[maxn];
int cmd[maxn], L[maxn], R[maxn];
int n, m, p;

void pushup(int root)
{
    tree[root] = tree[root*2]+tree[root*2+1];
}

void pushdown(int root, int l, int r)
{
    int mid = (l+r)/2;
    if(set[root] != -1)
    {
        set[root*2] = set[root*2+1] = set[root];
        tree[root*2] = set[root]*(mid-l+1);
        tree[root*2+1] = set[root]*(r-mid);
        set[root] = -1;
    }
}

void update(int root, int l, int r, int pos, int val)
{
    if(l == r)
    {
        tree[root] = val;
        return ;
    }
    int mid = (l+r)/2;
    if(pos <= mid) update(root*2, l, mid, pos, val);
    else update(root*2+1, mid+1, r, pos, val);
    pushup(root);
}

void Set(int root, int l, int r, int i, int j, int val)
{
    if(i <= l && j >= r)
    {
        set[root] = val;
        tree[root] = val*(r-l+1);
        return ;
    }
    pushdown(root, l, r);
    int mid = (l+r)/2;
    if(i <= mid) Set(root*2, l, mid, i, j, val);
    if(j > mid) Set(root*2+1, mid+1, r, i, j, val);
    pushup(root);
}

int query(int root, int l, int r, int i, int j)
{
    if(i <= l && j >= r)
        return tree[root];
    pushdown(root, l, r);
    int sum = 0;
    int mid = (l+r)/2;
    if(i <= mid) sum += query(root*2, l, mid, i, j);
    if(j > mid) sum += query(root*2+1, mid+1, r, i, j);
    return sum;
}


bool judge(int x)
{
    memset(set, -1, sizeof(set));
    for(int i = 1; i <= n; i++)
    {
        if(a[i] >= x) update(1, 1, n, i, 1);
        else update(1, 1, n, i, 0);
    }
    for(int i = 1; i <= m; i++)
    {
        int num = R[i]-L[i]+1;
        int big = query(1, 1, n, L[i], R[i]);
        if(cmd[i])
        {
            if(L[i]+big-1 >= L[i])
                Set(1, 1, n, L[i], L[i]+big-1, 1);
            if(L[i]+big <= R[i])
                Set(1, 1, n, L[i]+big, R[i], 0);
        }
        else
        {
            if(R[i]-big+1 <= R[i])
                Set(1, 1, n, R[i]-big+1, R[i], 1);
            if(R[i]-big >= L[i])
                Set(1, 1, n, L[i], R[i]-big, 0);
        }
    }
    return query(1, 1, n, p, p);
}

int main(void)
{
    while(cin >> n >> m)
    {
        int l = INF, r = -INF;
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]), l = min(l, a[i]), r = max(r, a[i]);
        for(int i = 1; i <= m; i++)
            scanf("%d%d%d", &cmd[i], &L[i], &R[i]);
        scanf("%d", &p);
        int ans = a[1];
        while(l <= r)
        {
            int mid = (l+r)/2;
            if(judge(mid)) ans = mid, l = mid+1;
            else r = mid-1;
        }
        printf("%d\n", ans);
    }
    return 0;
}



你可能感兴趣的:(二分,线段树)