洛谷 P3380 【模板】二逼平衡树(树套树) 题解
一、题目:
二、思路:
很明显这题是道模板题。在此采用常规思路,即线段树套平衡树。
具体来说就是线段树的每一个节点x是一棵平衡树,这棵平衡树中的元素就是x所管理的区间中的元素。可以简记为,外层维护位置,内层维护元素。
而线段树的作用就是当我们查询某一个区间\([L,R]\)时,该区间可以被线段树划分成若干个小区间\([l_i, r_i]\),我们分别在这些小区间所对应的平衡树们上查询答案,最终再将这些答案合并起来,得到最终大区间的答案。
更具体地:
- 求排名:加和。时间复杂度:\(O(\log^2 N)\)
- 求前驱:取max。时间复杂度:\(O(\log^2 N)\)
- 求后继:取min。时间复杂度:\(O(\log^2 N)\)
- 求第k小:这个问题比较麻烦,不能直接合并。我们考虑二分答案,即二分出来一个值mid,看一看Rank(mid)和k的关系,再去调整二分区间。时间复杂度:\(O(\log^2N \times \log maxv)\)
当然,求第k小的操作复杂度有一些高。我们可以采用权值线段树套平衡树,即外层维护值,内层维护位置的方法来降低复杂度。博主以后可能会实现这种方法。
三、代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define mem(s, v) memset(s, v, sizeof s)
inline int read(void) {
register int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 5e6 + 5, inf = 2147483647;
int a[maxn], n, m;
namespace Balance_Tree {
int son[maxn][2], fa[maxn], root[maxn], val[maxn], siz[maxn], cnt[maxn];
int sz;
inline void update(int x) {
siz[x] = siz[son[x][0]] + siz[son[x][1]] + cnt[x];
}
inline int get(int x) { return son[fa[x]][1] == x; }
inline void rotate(int x) {
int y = fa[x], z = fa[y], k = get(x);
son[y][k] = son[x][k ^ 1]; fa[son[y][k]] = y; fa[y] = x;
son[x][k ^ 1] = y; fa[x] = z;
if (z) son[z][son[z][1] == y] = x;
update(y); update(x);
}
inline void splay(int i, int x, int goal) {
for (int f; (f = fa[x]) != goal; rotate(x)) {
if (fa[f] != goal) rotate((get(x) == get(f)) ? f : x);
}
if (!goal) root[i] = x;
}
inline int kth(int i, int k) {
++k;
int now = root[i];
while (233) {
if (siz[son[now][0]] >= k) now = son[now][0];
else {
k -= siz[son[now][0]];
if (cnt[now] >= k) return now;
k -= cnt[now];
now = son[now][1];
}
}
}
inline int pre(int i, int x) {
int now = root[i], ret = 0;
while (now) {
if (val[now] == x) {
if (son[now][0]) {
now = son[now][0];
while (son[now][1]) now = son[now][1];
ret = now;
}
break;
}
if (val[now] < x && (!ret || val[now] > val[ret])) ret = now;
now = son[now][x > val[now]];
}
return ret;
}
inline int nxt(int i, int x) {
int now = root[i], ret = 0;
while (now) {
if (val[now] == x) {
if (son[now][1]) {
now = son[now][1];
while (son[now][0]) now = son[now][0];
ret = now;
}
break;
}
if (val[now] > x && (!ret || val[now] < val[ret])) ret = now;
now = son[now][x > val[now]];
}
return ret;
}
inline void find(int i, int x) {
int now = root[i], ret = 0;
while (now) {
if (val[now] == x) {
ret = now; break;
}
if (val[now] > x && (!ret || val[now] < val[ret])) ret = now;
now = son[now][x > val[now]];
}
splay(i, ret, 0);
}
inline int Rank(int i, int x) {
find(i, x);
return siz[son[root[i]][0]];
}
inline void insert(int i, int x) {
if (!root[i]) {
val[++sz] = x; siz[sz] = cnt[sz] = 1;
root[i] = sz;
return;
}
find(i, x);
if (val[root[i]] == x) { ++cnt[root[i]]; return; }
val[++sz] = x; cnt[sz] = 1;
son[sz][0] = son[root[i]][0]; fa[son[sz][0]] = sz; fa[sz] = root[i];
son[root[i]][0] = sz;
splay(i, sz, 0);
}
inline void del(int i, int x) {
find(i, x);
if (cnt[root[i]] > 1) { --cnt[root[i]]; return; }
int tmp1, tmp2;
tmp1 = pre(i, x); splay(i, tmp1, root[i]);
tmp2 = son[root[i]][1];
fa[tmp2] = tmp1;
son[tmp1][1] = tmp2; fa[tmp1] = 0;
root[i] = tmp1; update(tmp1);
}
}
namespace Segment_Tree {
#define lson (o << 1)
#define rson (o << 1 | 1)
int L[maxn], R[maxn];
inline void build(int o, int l, int r) {
L[o] = l; R[o] = r;
Balance_Tree::insert(o, inf); Balance_Tree::insert(o, -inf);
if (l == r) return;
int mid = (l + r) >> 1;
build(lson, l, mid); build(rson, mid + 1, r);
}
inline int Rank(int o, int ql, int qr, int v) {
if (ql <= L[o] && R[o] <= qr) { return Balance_Tree::Rank(o, v) - 1;/*注意不能直接加和*/ }
int mid = (L[o] + R[o]) >> 1, ret = 0;
if (ql <= mid) ret += Rank(lson, ql, qr, v);
if (qr > mid) ret += Rank(rson, ql, qr, v);
return (o == 1) ? ret + 1 : ret;
}
inline void update(int o, int q, int v) {
if (L[o] == R[o]) { if (~a[q]) Balance_Tree::del(o, a[q]); Balance_Tree::insert(o, v); return; }
int mid = (L[o] + R[o]) >> 1;
if (q <= mid) update(lson, q, v);
else update(rson, q, v);
if (~a[q]) Balance_Tree::del(o, a[q]); Balance_Tree::insert(o, v);
if (o == 1) a[q] = v;
}
inline int Pre(int o, int ql, int qr, int v) {
if (ql <= L[o] && R[o] <= qr) { return Balance_Tree::val[Balance_Tree::pre(o, v)]; }
int mid = (L[o] + R[o]) >> 1, ret = -inf;//ret赋初值要赋为-inf
if (ql <= mid) ret = max(ret, Pre(lson, ql, qr, v));
if (qr > mid) ret = max(ret, Pre(rson, ql, qr, v));
return ret;
}
inline int Nxt(int o, int ql, int qr, int v) {
if (ql <= L[o] && R[o] <= qr) { return Balance_Tree::val[Balance_Tree::nxt(o, v)]; }
int mid = (L[o] + R[o]) >> 1, ret = inf;
if (ql <= mid) ret = min(ret, Nxt(lson, ql, qr, v));
if (qr > mid) ret = min(ret, Nxt(rson, ql, qr, v));
return ret;
}
inline int kth(int o, int ql, int qr, int k) {
int l = 0, r = 1e8;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (Rank(1, ql, qr, mid) <= k) l = mid;
else r = mid - 1;
}
return l;
}
}
int main() {
mem(a, -1);
n = read(); m = read();
Segment_Tree::build(1, 1, n);
for (register int i = 1; i <= n; ++i) {
Segment_Tree::update(1, i, read());
}
int opt, l, r, k;
while (m--) {
opt = read();
switch(opt) {
case 1: l = read(); r = read(); k = read(); printf("%d\n", Segment_Tree::Rank(1, l, r, k)); break;
case 2: l = read(); r = read(); k = read(); printf("%d\n", Segment_Tree::kth(1, l, r, k)); break;
case 3: l = read(); k = read(); Segment_Tree::update(1, l, k); break;
case 4: l = read(); r = read(); k = read(); printf("%d\n", Segment_Tree::Pre(1, l, r, k)); break;
case 5: l = read(); r = read(); k = read(); printf("%d\n", Segment_Tree::Nxt(1, l, r, k)); break;
}
}
return 0;
}