bzoj4552[HEOI2016/TJOI2016]排序(二分+线段树)

本文介绍了一种利用二分查找结合线段树数据结构优化排序问题的算法。通过对序列进行特殊划分,将排序问题转化为01序列排序,从而大幅降低排序的复杂度。文章详细解释了算法原理,包括如何通过二分查找确定可能的答案,并使用线段树进行区间查询和更新,以判断特定位置元素的相对大小。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

BZOJ

洛谷

解析

直接对一个序列排序复杂度太高,但对一个\(01\)序列排序就很简单

假设我们认定答案就是\(x\),那么可以将\(1\)\(n\)\(n\)个数分成\(3\)部分:
\[ (1) a[i] < x \\ (2) a[i] = x \\ (3) a[i] > x \]
排序时我们只关心三部分之间的大小关系,完全可以不管\((1)\)内部和\((3)\)内部怎么排

再进一步合并\((1)\)\((2)\),那么如果排序后\(q\)位置的数在\((1) \cup (2)\)中,那么\(x\)就可能是答案,如果\(q\)位置的数在\((3)\)中,显然\(x\)不是答案,答案一定大于\(x\)

于是可以二分这个\(x\),然后验证即可

验证的方法是将小于等于\(x\)的数看作\(0\),大于\(x\)的数看作\(1\),用线段树维护区间内\(0\)的个数,排序一个区间就先查询区间内\(0\)的个数\(cnt\),然后把\(l\)\(l + cnt - 1\)修改为\(0\)\(l + cnt\)\(r\)修改为\(1\),最后查询\(q\)位置是否为\(0\)即可

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

代码

PS.以下代码自带大常数,洛谷5000+ms卡着过

#include <cstdio>
#include <cstring>
#include <iostream>
#define MAXN 100005

typedef long long LL;
struct SegmentTree {
    int cnt0[MAXN << 2], cover[MAXN << 2];
    void pushDown(int, int, int);
    void build(int, int, int);
    int query(int, int, int, int, int);
    void update(int, int, int, int, int, int);
} tree;
int N, M, Q, op[MAXN], left[MAXN], right[MAXN];
int a[MAXN], b[MAXN];

bool check(int);
int main() {
    std::ios::sync_with_stdio(false);
    std::cin >> N >> M;
    for (int i = 1; i <= N; ++i)
        std::cin >> a[i];
    for (int i = 1; i <= M; ++i)
        std::cin >> op[i] >> left[i] >> right[i];
    std::cin >> Q;
    int l = 1, r = N;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    std::cout << l << std::endl;
    
    return 0;
}
void SegmentTree::pushDown(int id, int L, int R) {
    if (cover[id] == -1) return;
    int mid = (L + R) >> 1;
    if (cover[id]) cnt0[id << 1] = cnt0[id << 1 | 1] = 0;
    else cnt0[id << 1] = mid - L + 1, cnt0[id << 1 | 1] = R - mid;
    cover[id << 1] = cover[id << 1 | 1] = cover[id];
    cover[id] = -1;
}
void SegmentTree::build(int id, int L, int R) {
    cover[id] = -1;
    if (L == R) cnt0[id] = (b[L] == 0);
    else {
        int mid = (L + R) >> 1;
        build(id << 1, L, mid);
        build(id << 1 | 1, mid + 1, R);
        cnt0[id] = cnt0[id << 1] + cnt0[id << 1 | 1];
    }
}
void SegmentTree::update(int id, int L, int R, int l, int r, int tp) {
    if (l > r) return;
    if (L >= l && R <= r) {
        cover[id] = tp;
        cnt0[id] = (tp ? 0 : R - L + 1);
    } else {
        pushDown(id, L, R);
        int mid = (L + R) >> 1;
        if (l <= mid) update(id << 1, L, mid, l, r, tp);
        if (r > mid) update(id << 1 | 1, mid + 1, R, l, r, tp);
        cnt0[id] = cnt0[id << 1] + cnt0[id << 1 | 1];
    }
}
int SegmentTree::query(int id, int L, int R, int l, int r) {
    if (L >= l && R <= r) return cnt0[id];
    pushDown(id, L, R);
    int mid = (L + R) >> 1, res = 0;
    if (l <= mid) res += query(id << 1, L, mid, l, r);
    if (r > mid) res += query(id << 1 | 1, mid + 1, R, l, r);
    return res;
}
bool check(int m) {
    for (int i = 1; i <= N; ++i)
        b[i] = (a[i] > m);
    tree.build(1, 1, N);
    for (int i = 1; i <= M; ++i) {
        int t = tree.query(1, 1, N, left[i], right[i]);
        if (op[i]) tree.update(1, 1, N, right[i] - t + 1, right[i], 0), tree.update(1, 1, N, left[i], right[i] - t, 1);
        else tree.update(1, 1, N, left[i], left[i] + t - 1, 0), tree.update(1, 1, N, left[i] + t, right[i], 1);
    }
    return tree.query(1, 1, N, Q, Q);
}

转载于:https://www.cnblogs.com/Rhein-E/p/10428145.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值