Treap \text{Treap} Treap 是一种平衡树但是节点没有 parent \text{parent} parent 域(其实可以有,但是这里没必要),对于插入节点和普通的搜索树一样,在插入完成后要检查优先级是否满足大根堆的性质,如果新插入的节点不满足这个性质就旋转,将优先级高的转上来,删除节点分为两种情况,一种是还没找到要删除的节点的时候,就递归地寻找,如果找到了,调用另一个函数,删除一个 Treap \text{Treap} Treap 的根节点(对于一个 Treap \text{Treap} Treap 的根节点来说他的左右孩子要么是空,要么也是一棵 Treap \text{Treap} Treap ),将左右孩子中优先级较高的旋转上来,然后递归地去删除被旋转下去的那个原先的根节点,直到被旋转到叶子节点,就直接删除。
template <typename T> struct Node {
Node *lc, *rc;
T key;
int priority;
};
对于关键词, Treap \text{Treap} Treap 是一棵二叉搜索树,对于优先级(随机分配), Treap \text{Treap} Treap 满足大根堆的性质,为了方便,所有空指针可以设置为优先级 − ∞ -\infty −∞ 的节点。
插入一个优先级为 + ∞ +\infty +∞ 的节点,关键词设置为 x x x ,那么根节点的左右两棵 Treap \text{Treap} Treap 的所有关键词分别为小于 x x x 和大于 x x x (不允许重复关键词的情况)。
合并两棵 Treap \text{Treap} Treap 要求左边的树的所有关键词都小于右边的树,随便设置一个节点 x x x 使 x . left x.\text{left} x.left 和 x . right x.\text{right} x.right 分别为两棵 Treap \text{Treap} Treap ,然后删除根节点。
P6136 【模板】普通平衡树(数据加强版)
数组的简化实现。。用了 mt19937
32 位梅森缠绕器。
#include
using namespace std;
using ll = long long;
const int N = 1100005;
mt19937 gen(time(NULL));
struct Treap {
static inline int ch[N][2], pri[N], val[N], siz[N], cnt[N], node_tot;
int root;
Treap() : root(0) { pri[0] = INT_MIN; }
void pushup(int x) { siz[x] = siz[ch[x][0]] + cnt[x] + siz[ch[x][1]]; }
int newnode(int v) {
pri[++node_tot] = gen();
val[node_tot] = v;
siz[node_tot] = cnt[node_tot] = 1;
return node_tot;
}
void rotate(int &x, int d) { // d=0左旋,d=1右旋
int t = ch[x][1 ^ d];
ch[x][1 ^ d] = ch[t][d], ch[t][d] = x;
siz[t] = siz[x];
pushup(x);
x = t;
}
void insert(int &x, int v) {
if (!x) {
x = newnode(v);
} else if (val[x] == v) {
++cnt[x], ++siz[x];
} else {
++siz[x];
int t = v > val[x];
insert(ch[x][t], v);
if (pri[ch[x][t]] > pri[x]) rotate(x, 1 ^ t); // 维持大根堆特性
}
}
void delroot(int &x) {
if (ch[x][0] == ch[x][1]) {
x = 0;
} else {
int t = pri[ch[x][0]] > pri[ch[x][1]]; // 左边的优先级>右边的优先级就右旋
rotate(x, t);
delroot(ch[x][t]); // 如果前面是右旋的话:x已经变成左边的节点了,x的右孩子就是原先的节点
}
}
void del(int &x, int v) {
if (val[x] == v) {
--siz[x];
if (!--cnt[x]) delroot(x); // 出现次数为0了就删掉这个节点
} else {
--siz[x];
del(ch[x][v > val[x]], v);
}
}
int rank(int v) {
int ans = 0, x = root;
while (x) {
if (v > val[x]) {
ans += siz[ch[x][0]] + cnt[x];
x = ch[x][1];
} else if (v == val[x]) {
ans += siz[ch[x][0]];
break;
} else {
x = ch[x][0];
}
}
return ans;
}
int kth(int k) {
int x = root;
while (k <= siz[ch[x][0]] || k > siz[ch[x][0]] + cnt[x]) {
if (k <= siz[ch[x][0]]) {
x = ch[x][0];
} else {
k -= siz[ch[x][0]] + cnt[x], x = ch[x][1];
}
}
return val[x];
}
int pred(int v) {
int x = root, ans = -0x3f3f3f3f;
while (x) {
if (val[x] < v) {
ans = val[x];
x = ch[x][1];
} else {
x = ch[x][0];
}
}
return ans;
}
int succ(int v) {
int x = root, ans = 0x3f3f3f3f;
while (x) {
if (val[x] > v) {
ans = val[x];
x = ch[x][0];
} else {
x = ch[x][1];
}
}
return ans;
}
} t;
int main() {
#ifdef LOCAL
freopen("..\\in", "r", stdin), freopen("..\\out", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, v;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> v;
t.insert(t.root, v);
}
int last = 0, ans = 0, cmd;
while (m--) {
cin >> cmd >> v;
v ^= last;
switch (cmd) {
case 1: // 插入 xx 数
t.insert(t.root, v);
break;
case 2: // 删除 xx 数(若有多个相同的数,只删除一个)
t.del(t.root, v);
break;
case 3: // 查询 xx 数的排名(排名定义为比当前数小的数的个数 +1 )
ans ^= (last = t.rank(v) + 1);
break;
case 4: // 查询排名为 xx 的数
ans ^= (last = t.kth(v));
break;
case 5: // 求 xx 的前驱(前驱定义为小于 xx,且最大的数)
ans ^= (last = t.pred(v));
break;
case 6: // 求 xx 的后继(后继定义为大于 xx,且最小的数)
ans ^= (last = t.succ(v));
break;
}
}
cout << ans;
return 0;
}
题目:P3380 【模板】二逼平衡树(树套树)
比以前减少了一些常数。
#include
using namespace std;
using ll = long long;
const int N = 5e4 + 5, INF = INT_MAX;
mt19937 gen(time(NULL));
struct Treap {
static inline int ch[N << 7][2], pri[N << 7], val[N << 7], siz[N << 7], cnt[N << 7], node_tot;
int root;
Treap() : root(0) { pri[0] = INT_MIN; }
void pushup(int x) { siz[x] = siz[ch[x][0]] + cnt[x] + siz[ch[x][1]]; }
int newnode(int v) {
pri[++node_tot] = gen();
val[node_tot] = v;
siz[node_tot] = cnt[node_tot] = 1;
return node_tot;
}
void rotate(int &x, int d) { // d=0左旋,d=1右旋
int t = ch[x][1 ^ d];
ch[x][1 ^ d] = ch[t][d], ch[t][d] = x;
siz[t] = siz[x];
pushup(x);
x = t;
}
void insert(int &x, int v) {
if (!x) {
x = newnode(v);
} else if (val[x] == v) {
++cnt[x], ++siz[x];
} else {
++siz[x];
int t = v > val[x];
insert(ch[x][t], v);
if (pri[ch[x][t]] > pri[x]) rotate(x, 1 ^ t); // 维持大根堆特性
}
}
void delroot(int &x) {
if (ch[x][0] == ch[x][1]) {
x = 0;
} else {
int t = pri[ch[x][0]] > pri[ch[x][1]]; // 左边的优先级>右边的优先级就右旋
rotate(x, t);
delroot(ch[x][t]); // 如果前面是右旋的话:x已经变成左边的节点了,x的右孩子就是原先的节点
}
}
void del(int &x, int v) {
if (val[x] == v) {
--siz[x];
if (!--cnt[x]) delroot(x); // 出现次数为0了就删掉这个节点
} else {
--siz[x];
del(ch[x][v > val[x]], v);
}
}
int rank(int v) {
int ans = 0, x = root;
while (x) {
if (v > val[x]) {
ans += siz[ch[x][0]] + cnt[x];
x = ch[x][1];
} else if (v == val[x]) {
ans += siz[ch[x][0]];
break;
} else {
x = ch[x][0];
}
}
return ans;
}
int kth(int k) {
int x = root;
while (k <= siz[ch[x][0]] || k > siz[ch[x][0]] + cnt[x]) {
if (k <= siz[ch[x][0]]) {
x = ch[x][0];
} else {
k -= siz[ch[x][0]] + cnt[x], x = ch[x][1];
}
}
return val[x];
}
int pred(int v) {
int x = root, ans = -INF;
while (x) {
if (val[x] < v) {
ans = val[x];
x = ch[x][1];
} else {
x = ch[x][0];
}
}
return ans;
}
int succ(int v) {
int x = root, ans = INF;
while (x) {
if (val[x] > v) {
ans = val[x];
x = ch[x][0];
} else {
x = ch[x][1];
}
}
return ans;
}
} tree[N << 2];
int a[N];
void build(int o, int l, int r) {
for (int i = l; i <= r; ++i) {
tree[o].insert(tree[o].root, a[i]);
}
if (l == r) return;
int mid = l + r >> 1;
build(o << 1, l, mid);
build(o << 1 | 1, mid + 1, r);
}
void modify(int o, int l, int r, int x, int v) {
tree[o].del(tree[o].root, a[x]);
tree[o].insert(tree[o].root, v);
if (l == r) return;
int mid = l + r >> 1;
x <= mid ? modify(o << 1, l, mid, x, v) : modify(o << 1 | 1, mid + 1, r, x, v);
}
int queryrank(int o, int l, int r, int x, int y, int v) {
if (x <= l && y >= r) return tree[o].rank(v);
int mid = l + r >> 1, ans = 0;
if (x <= mid) ans += queryrank(o << 1, l, mid, x, y, v);
if (y > mid) ans += queryrank(o << 1 | 1, mid + 1, r, x, y, v);
return ans;
}
int querypred(int o, int l, int r, int x, int y, int v) {
if (x <= l && y >= r) return tree[o].pred(v);
int mid = l + r >> 1, ans = -INF;
if (x <= mid) ans = max(ans, querypred(o << 1, l, mid, x, y, v));
if (y > mid) ans = max(ans, querypred(o << 1 | 1, mid + 1, r, x, y, v));
return ans;
}
int querysucc(int o, int l, int r, int x, int y, int v) {
if (x <= l && y >= r) return tree[o].succ(v);
int mid = l + r >> 1, ans = INF;
if (x <= mid) ans = min(ans, querysucc(o << 1, l, mid, x, y, v));
if (y > mid) ans = min(ans, querysucc(o << 1 | 1, mid + 1, r, x, y, v));
return ans;
}
int main() {
#ifdef LOCAL
freopen("..\\in", "r", stdin), freopen("..\\out", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
build(1, 1, n);
while (m--) {
switch (int cmd, l, r, k; (cin >> cmd), cmd) {
case 1: // 若opt=1 则为操作1,之后有三个数l,r,k 表示查询k在区间[l,r]的排名
cin >> l >> r >> k;
cout << queryrank(1, 1, n, l, r, k) + 1 << '\n';
break;
case 2: // 若opt=2 则为操作2,之后有三个数l,r,k 表示查询区间[l,r]内排名为k的数
cin >> l >> r >> k;
{
int low = 0, high = 100000000;
while (low < high) {
int mid = low + high >> 1, t = queryrank(1, 1, n, l, r, mid);
if (t < k) {
low = mid + 1;
} else {
high = mid;
}
}
cout << low - 1 << '\n';
}
break;
case 3: // 若opt=3 则为操作3,之后有两个数pos,k 表示将pos位置的数修改为k
cin >> l >> k;
modify(1, 1, n, l, k);
a[l] = k;
break;
case 4: // 若opt=4 则为操作4,之后有三个数l,r,k 表示查询区间[l,r]内k的前驱
cin >> l >> r >> k;
cout << querypred(1, 1, n, l, r, k) << '\n';
break;
case 5: // 若opt=5 则为操作5,之后有三个数l,r,k 表示查询区间[l,r]内k的后继
cin >> l >> r >> k;
cout << querysucc(1, 1, n, l, r, k) << '\n';
break;
}
}
return 0;
}
中文叫笛卡尔树,笛卡尔树可以简单地定义,给定一个数组 A A A 长度为 n n n (下标从 0 0 0 开始),他的笛卡尔树的根是 A A A 中的最小值,设为 A min A_{\min} Amin ( min \min min 是位置的下标),左子树和右子树分别为 A 0 , A 1 , ⋯ , A min − 1 A_0,A_1,\cdots,A_{\min-1} A0,A1,⋯,Amin−1 的笛卡尔树和 A min + 1 , A min + 2 , ⋯ , A n − 1 A_{\min+1},A_{\min+2},\cdots,A_{n-1} Amin+1,Amin+2,⋯,An−1 的笛卡尔树,空数组的笛卡尔树也为空树。
按照定义的话用线段树或者 ST \text{ST} ST 表都可以随便构造,但是复杂度比较高。
构造笛卡尔树可以用栈(单调栈),一直退栈直到栈为空或者栈顶元素比当前元素小,当前元素的左孩子为刚刚最后一次退栈的元素(如果栈在执行 pop \text{pop} pop 之前就是空就是空),当前元素的双亲是当前栈顶元素(如果栈现在为空则为空,用这个可以更新笛卡尔树的根),复杂度为 Θ ( n ) \Theta(n) Θ(n) , Treap \text{Treap} Treap 在某种意义上也是笛卡尔树。
笛卡尔树可以在线性时间把后缀数组转换为后缀树(待补充)。
RMQ \text{RMQ} RMQ 中询问 [ A l , A r ] [A_l,A_r] [Al,Ar] 中的最小值,显然是 A l , A r A_l,A_r Al,Ar 笛卡尔树中分别对应的两个节点的最近公共祖先的值。
PS
在一些应用中不需要构造出笛卡尔树,用栈模拟就行。
题目:P5854 【模板】笛卡尔树
#include
using namespace std;
using ll = long long;
const int N = 1e7 + 5;
int a[N]; // 原数组
int stk[N], top = 0; // 栈
int lc[N], rc[N], fa[N]; // 树
void build(int n) {
for (int i = 1, last; i <= n; ++i) {
last = 0;
while (top && a[i] < a[stk[top - 1]]) last = stk[--top];
fa[lc[i] = last] = i;
if (top) rc[fa[i] = stk[top - 1]] = i;
stk[top++] = i;
}
}
int main() {
#ifdef LOCAL
freopen("..\\in", "r", stdin), freopen("..\\out", "w", stdout);
#endif
int n;
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
build(n);
ll l = 0, r = 0;
for (int i = 1; i <= n; ++i) {
l ^= static_cast<ll>(i) * static_cast<ll>(lc[i] + 1);
r ^= static_cast<ll>(i) * static_cast<ll>(rc[i] + 1);
}
cout << l << ' ' << r;
return 0;
}