线段树的奇幻科技——线段树上二分

线段树如果加上一个操作,询问在 \([l,r]\) 中第一个大于或小于某个数的位置,你会怎么做。

显然的一种想法是,维护一个区间 \(\min,\max\) 然后二分长度,每次 Query。但是明显是两只 \(\log\) 的,太慢啦,有没有快一点的方法呢。当然有。

还是维护区间 \(\min, \max\) 我们假设我们要找到 \([l,r]\) 中第一个小于 \(l\) 的位置。

首先的,如果左儿子的最小值小于 \(l\),那么答案就在左边,不然的话再去右边搜,这样的话保证每个地方只会搜到一边,那么我们就用 \(\mathcal{O}(n\log n)\) 的复杂度解决了这个问题。

代码

struct Segment_Tree {
    int lc[N << 2], rc[N << 2], minn[N << 2];
    void pushup(int u) {minn[u] = min(minn[u << 1], minn[u << 1 | 1]);}
    void build(int u, int l, int r) {
        lc[u] = l, rc[u] = r;
        if(l == r) {
            minn[u] = 0;
            return;
        }
        build(u << 1, l, (l + r) >> 1), build(u << 1 | 1, (l + r >> 1) + 1, r);
        pushup(u);
    }
    int search(int u, int l, int k) {
        //Find the first element < k in [l, r]
        if(lc[u] == rc[u]) {
            if(minn[u] >= k) return -1;
            return lc[u];
        }
        if(minn[u] >= k) return -1;
        if(rc[u] < l) return -1;
        int res = search(u << 1, l, k);
        if(res != -1) return res;
        return search(u << 1 | 1, l, k);
    }
    void update(int u, int p, int va) {
        if(lc[u] == rc[u]) {
            //cout << ' ' << lc[u] << ' ' << rc[u] << ' ' << u << endl;
            minn[u] = va;
            return;
        }
        if(p <= (lc[u] + rc[u] >> 1)) update(u << 1, p, va);
        else update(u << 1 | 1, p, va);
        pushup(u);
        //cout << u << ' ' << lc[u] << ' ' << rc[u] << ' ' << minn[u] << endl;
    }
}seg;
posted @ 2022-09-13 19:48  Mercury_City  阅读(1734)  评论(1编辑  收藏  举报