线段树的奇幻科技——线段树上二分
线段树如果加上一个操作,询问在 \([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;