FHQTreap
普通のバランスの取れたツリー
あなたは、次を提供する必要があるいくつかの番号を、維持するためのデータ構造を記述する必要があります。
- 挿入(X \)\数
- 削除\(X \) (のみ削除するので、複数の場合は、同じ番号)番号を
- クエリ(X \)\(ランク数が小さいランク数の現在の数の数の比として定義される\(1 + \)数。同じ複数の場合、最小ランクの出力ので)
- ランク付けされたクエリ\(X \)数
- 探している\(Xを\)の前駆体(以下のように定義されている前駆体\(X \) 、および最大数)
- 探している\(X \) (以降より大きいとして定義後続の\(X \) 、および最小数)
(\ \テキスト{FHQ Treap =ツリー+ヒープ} \)
この優先順位として、ノードに割り当てられた乱数によってFHQTreap、できるだけ木をランダム化することにより、バランスのバランスように。うれしい驚きは、注文FHQTreapトラバーサルで、元の配列を得ることができるということです。だから、どのように我々はFHQTreapそれを維持するのですか?
メモリ
我々は、次の変数を格納する必要があります。
struct FHQTreap{
int ch[N][2] /*0为左儿子 1为右儿子*/ , val[N] /*节点的值*/ , key[N] /*节点的优先级*/, siz[N] /* 以该节点为根的子树的大小(包括它本身)*/, root /*树的根*/, cnt /*节点个数*/;
} T;
アップロード
私たちは、操作を作っている、あなたは彼の息子の父親についての情報を広めるために必要がある場合があります。
inline void PushUp(re int rt) {
siz[rt] = siz[ch[rt][0]] + siz[ch[rt][1]] + 1; //以左儿子为根的子树+右儿子为根的子树+它本身即为1
}
新しいノード
新しいノードを作成します。
inline int NewNode(re int v) {// v 为新建的节点的值
siz[++cnt] = 1; val[cnt] = v; key[cnt] = rand()/*随机一个优先级*/;
return cnt;
}
サブツリーをマージ
二つのサブツリーをマージする重要な操作、。
サブツリーがあれば私たちは、ここでは2つのサブツリーを結合する規定を優先に従ってください\(X \)よりも優先\(のy \)が高い(\(key_x <key_y \) )、その後、我々はサブツリーます\を( yは\) 、サブツリーにマージ\(のx \)右の息子に。そうでなければ、我々はサブツリーます\(のx \)をサブツリーにマージ\(のy \)左の息子に。合併後は、親ノードに変更された情報をアップロードする必要があります。
int Merge(re int x, re int y) {//将x和y合并返回合并后的子树的编号
if (!x || !y) return x + y;//如果有任何一个子树为空,那么返回另一个(如果两个都是空返回0)
if (key[x] < key[y]) {
ch[x][1] = Merge(ch[x][1], y);
PushUp(x); return x;
} else {
ch[y][0] = Merge(x, ch[y][0]);
PushUp(y); return y;
}
}
スプリットサブツリー
一つは重量区分に基づいており、そして他方は点のサイズに基づいている:重要サブツリーをマージするように、我々は2つの方法に分割しました。この質問は、最も便利な方法を使用する権利、文学バランスの取れたツリー話すの点の大きさの値を分割することであるです。
重みでどのようにそれを分割するには?我々は、すべての点未満または分割サブツリーの左に渡された平等な権利ポイントパラメータ、右部分木に他の分割することができます。再帰的なソリューションをすることができ、最終的に我々は、分割サブツリーの後に情報をアップロードします。
void Split(re int rt, re int v, re int &x, re int &y) {//带&方便修改
if (!rt) x = y = 0;
else {
if (val[rt] <= v) {
x = rt;//分给右子树
Split(ch[rt][1], v, ch[rt][1], y);
} else {
y = rt; //分给左子树
Split(ch[rt][0], v, x, ch[rt][0]);
}
PushUp(rt);//上传
}
}
挿入ノード
我々は、中に木を入れ\(X、Yの\)最初で、二つの部分にして、新しいノードをツリーとして示されている(X \)\後合併、全体合わせて終了\(Y軸\)合併。
inline void Insert(re int v) {
re int x, y;
Split(root, v, x, y);
root = Merge(Merge(x, NewNode(v)), y);
}
ノードを削除します。
まず、我々はにツリーを入れて\(X \)と\(のz \) 、二つの部分のノードを削除するために私たちの権利を設定されている(\)を\、その後、\(X \)に\(X \)と\ (Y \)ように、2つの部分、で\(X \)すべてのノードの重み値よりも小さい\(\)、\ (Y \)全てよりも大きい\(\) 。これは、パラメータを渡すために、私たちと同じです\(V = A - 1 \) 。そして、右側の値は\(\)のノードを正確に\(Y \)ツリーのルート。その後、我々は無視することができます\(のy \)をルートノードで、直接\(のy \)を組み合わせ、子供については、これに成功し、ルートノードを削除し、最後に\(X、Y、Zは\ ) に吸収合併良いです。
inline void Delete(re int v) {
re int x, y, z;
Split(root, v, x, z);
Split(x, v - 1, x, y);
y = Merge(ch[y][0], ch[y][1]);
root = Merge(Merge(x, y), z);
}
ランキングの数を求めて
二分探索木の性質を考えてみましょう:値の左の息子、値が父親よりも父親の小さい方の右の息子よりも大きくなります。だから、ランキングの数は、すべてのは、それがプラス自身の数よりも小さいことです。
inline int Lvl(re int v) {
re int x, y, res;
Split(root, v - 1, x, y);
res = siz[x] + 1;
root = Merge(x, y); //分裂后别忘合并
return res;
}
数の任意の値の順位を求めて
从根节点出发。根据二叉查找树的性质,如果当前点的值比所在点的值大,那么向右子树走,否则向左子树走。如果排名正好相等就返回值,while
循环就可以解决。可是真的那么简单吗?不,我们向右子树走的时候,左子树会有许多比它权值小的节点,所以我们要减去它左子树的大小。向左子树走的话直接走就可以了。
inline int Kth(re int rt, re int v) {
while (1) {
if (v <= siz[ch[rt][0]])
rt = ch[rt][0];
else if (v == siz[ch[rt][0]] + 1)
return val[rt];
else {
v -= siz[ch[rt][0]] + 1;
rt = ch[rt][1];
}
}
}
求一个数的前驱
因为要小于 \(a\) ,那么我们按照 \(a-1\) 的权值分裂成 \(x\) 和 \(y\) ,\(x\) 中最大的一定是\(\leq a - 1\)的,所以我们直接输出 \(x\) 中最大的数即可。
inline int Pre(re int v) {
re int x, y, res;
Split(root, v - 1, x, y);
res = Kth(x, siz[x]);
root = Merge(x, y);
return res;
}
求一个数的后继
与求前驱相似,我们只需修改找 \(\ge a\) 的最小的数就可以了。
inline int Suf(re int v) {
re int x, y, res;
Split(root, v, x, y);
res = Kth(y, 1);
root = Merge(x, y);
return res;
}
Code
#include <bits/stdc++.h>
#define N 100005
#define re register
#define inline inline
using namespace std;
int n;
struct FHQTreap {
int ch[N][2], val[N], key[N], siz[N], root, cnt;
inline void PushUp(re int rt) {
siz[rt] = siz[ch[rt][0]] + siz[ch[rt][1]] + 1;
}
inline int NewNode(re int v) {
siz[++cnt] = 1; val[cnt] = v; key[cnt] = rand();
return cnt;
}
int Merge(re int x, re int y) {
if (!x || !y) return x + y;
if (key[x] < key[y]) {
ch[x][1] = Merge(ch[x][1], y);
PushUp(x); return x;
} else {
ch[y][0] = Merge(x, ch[y][0]);
PushUp(y); return y;
}
}
void Split(re int rt, re int v, re int &x, re int &y) {
if (!rt) x = y = 0;
else {
if (val[rt] <= v) {
x = rt;
Split(ch[rt][1], v, ch[rt][1], y);
} else {
y = rt;
Split(ch[rt][0], v, x, ch[rt][0]);
}
PushUp(rt);
}
}
inline void Insert(re int v) {
re int x, y;
Split(root, v, x, y);
root = Merge(Merge(x, NewNode(v)), y);
}
inline void Delete(re int v) {
re int x, y, z;
Split(root, v, x, z);
Split(x, v - 1, x, y);
y = Merge(ch[y][0], ch[y][1]);
root = Merge(Merge(x, y), z);
}
inline int Lvl(re int v) {
re int x, y, res;
Split(root, v - 1, x, y);
res = siz[x] + 1;
root = Merge(x, y);
return res;
}
inline int Kth(re int rt, re int v) {
while (1) {
if (v <= siz[ch[rt][0]])
rt = ch[rt][0];
else if (v == siz[ch[rt][0]] + 1)
return val[rt];
else {
v -= siz[ch[rt][0]] + 1;
rt = ch[rt][1];
}
}
}
inline int Pre(re int v) {
re int x, y, res;
Split(root, v - 1, x, y);
res = Kth(x, siz[x]);
root = Merge(x, y);
return res;
}
inline int Suf(re int v) {
re int x, y, res;
Split(root, v, x, y);
res = Kth(y, 1);
root = Merge(x, y);
return res;
}
} T;
int main() {
scanf("%d", &n);
for (re int i = 1; i <= n; i++) {
re int opt, x;
scanf("%d %d", &opt, &x);
if (opt == 1) T.Insert(x);
else if (opt == 2) T.Delete(x);
else if (opt == 3) printf("%d\n", T.Lvl(x));
else if (opt == 4) printf("%d\n", T.Kth(T.root, x));
else if (opt == 5) printf("%d\n", T.Pre(x));
else if (opt == 6) printf("%d\n", T.Suf(x));
}
return 0;
}
文艺平衡树
您需要写一种数据结构(可参考题目标题),来维护一个有序排列,其中需要提供以下操作:翻转一个区间(没了)。
懒标记下传
直接更新所有的儿子往往会超时,所以要用懒标记,用一个数,这个数为 \(0\) 或 \(1\),用来表示是否需要反转。
inl void PushDown(reg int rt) {
if (rev[rt]) {
swap(ch[rt][0], ch[rt][1]);
if (ch[rt][0]) rev[ch[rt][0]] ^= 1;
if (ch[rt][1]) rev[ch[rt][1]] ^= 1;
rev[rt] = 0;
}
}
分裂子树
设给定的大小为 \(v\),那么我们把数分成 \(v\) 与 \(size_{rt} - x\)。那么分的策略就是:先找左子树,左子树多了分给右子树,不够去和右子树要。
void Split(reg int rt, reg int pos, reg int &x, reg int &y) {
if (!rt) x = y = 0;
else {
PushDown(rt);
if (pos <= siz[ch[rt][0]]) {
y = rt;
Split(ch[rt][0], pos, x, ch[rt][0]);
} else {
x = rt;
Split(ch[rt][1], pos - siz[ch[rt][0]] - 1, ch[rt][1], y);
}
PushUp(rt);
}
}
区间反转
我们将树的 \(l - 1\) 部分分给 \(x\),那么 \(y\) 表示的区间为 \([y,n]\)。再将 \(y\) 分裂出一个 \(z\),长度为 \(r - l + 1\)。
inl void Rev(reg int l, reg int r) {
reg int x, y, z;
Split(root, l - 1, x, y);
Split(y, r - l + 1, y, z);
rev[y] ^= 1;
root = Merge(x, Merge(y, z));
}
中序遍历
通过中序遍历得到反转后的序列。
void Print(reg int rt) {
if (!rt) return;
if (rev[rt]) PushDown(rt);
Print(ch[rt][0]);
printf("%d ", val[rt]);
Print(ch[rt][1]);
}
Code
#include <bits/stdc++.h>
#define MAXN 100005
#define reg register
#define inl inline
using namespace std;
int n, m;
struct FHQTreap {
int ch[MAXN][2], val[MAXN], pri[MAXN], siz[MAXN], rev[MAXN], root, cnt;
inl void PushUp(reg int rt) {
siz[rt] = siz[ch[rt][0]] + siz[ch[rt][1]] + 1;
}
inl void PushDown(reg int rt) {
if (rev[rt]) {
swap(ch[rt][0], ch[rt][1]);
if (ch[rt][0]) rev[ch[rt][0]] ^= 1;
if (ch[rt][1]) rev[ch[rt][1]] ^= 1;
rev[rt] = 0;
}
}
inl int NewNode(reg int v) {
siz[++cnt] = 1;
val[cnt] = v;
pri[cnt] = rand();
return cnt;
}
int Merge(reg int x, reg int y) {
if (!x || !y) return x + y;
if (pri[x] < pri[y]) {
if (rev[x]) PushDown(x);
ch[x][1] = Merge(ch[x][1], y);
PushUp(x);
return x;
} else {
if (rev[y]) PushDown(y);
ch[y][0] = Merge(x, ch[y][0]);
PushUp(y);
return y;
}
}
void Split(reg int rt, reg int pos, reg int &x, reg int &y) {
if (!rt) x = y = 0;
else {
PushDown(rt);
if (pos <= siz[ch[rt][0]]) {
y = rt;
Split(ch[rt][0], pos, x, ch[rt][0]);
} else {
x = rt;
Split(ch[rt][1], pos - siz[ch[rt][0]] - 1, ch[rt][1], y);
}
PushUp(rt);
}
}
inl void Rev(reg int l, reg int r) {
reg int x, y, z;
Split(root, l - 1, x, y);
Split(y, r - l + 1, y, z);
rev[y] ^= 1;
root = Merge(x, Merge(y, z));
}
void Print(reg int rt) {
if (!rt) return;
if (rev[rt]) PushDown(rt);
Print(ch[rt][0]);
printf("%d ", val[rt]);
Print(ch[rt][1]);
}
} T;
int main() {
scanf("%d %d", &n, &m);
for (reg int i = 1; i <= n; i++) T.root = T.Merge(T.root, T.NewNode(i));
for (reg int i = 1; i <= m; i++) {
reg int x, y;
scanf("%d %d", &x, &y);
T.Rev(x, y);
}
T.Print(T.root);
return 0;
}
可持久化文艺平衡树
您需要写一种数据结构,来维护一个序列,其中需要提供以下操作(对于各个以往的历史版本):
- 在第 \(p\) 个数后插入数 \(x\) 。
- 删除第 \(p\) 个数。
- 翻转区间 \([l,r]\),例如原序列是 \(\{5,4,3,2,1\}{5,4,3,2,1}\),翻转区间 \([2,4]\) 后,结果是 \(\{5,2,3,4,1\}\)。
- 查询区间 \([l,r]\) 中所有数的和。
和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本(操作 \(4\) 即保持原版本无变化),新版本即编号为此次操作的序号。
本题强制在线。
克隆节点
在每次合并与分裂的时候,需要新建一个节点修改,而不是在原来节点上修改。介绍一个小技巧,每次删除节点的时候,用一个队列记录这个点所占用的空间被 释放了,之后复制节点复制再这个位置上就可以了。
int Clone(int y) {
int x;
if (!q.empty()) {
x = q.front();
q.pop();
} else x = ++tot;
t[x] = t[y];
return x;
}
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct FHQTreap {
int son[2], val, tot, rev, key;
ll sum;
} t[200005 << 6];
queue <int> q;
int n, m, tot, root[500005];
int NewNode(int val) {
int x;
if (!q.empty()) {
x = q.front();
q.pop();
} else x = ++tot;
t[x].val = t[x].sum = val;
t[x].tot = 1;
t[x].key = rand();
t[x].son[0] = t[x].son[1] = t[x].rev = 0;
return x;
}
int Clone(int y) {
int x;
if (!q.empty()) {
x = q.front();
q.pop();
} else x = ++tot;
t[x] = t[y];
return x;
}
void Update(int x) {
t[x].tot = t[t[x].son[0]].tot + t[t[x].son[1]].tot + 1;
t[x].sum = t[t[x].son[0]].sum + t[t[x].son[1]].sum + t[x].val;
}
void PushDown(int x) {
if (t[x].rev) {
swap(t[x].son[0], t[x].son[1]);
if (t[x].son[0]) {
t[x].son[0] = Clone(t[x].son[0]);
t[t[x].son[0]].rev ^= 1;
}
if (t[x].son[1]) {
t[x].son[1] = Clone(t[x].son[1]);
t[t[x].son[1]].rev ^= 1;
}
t[x].rev = 0;
}
}
int Merge(int x, int y) {
if (!x || !y) return x + y;
if (t[x].key < t[y].key) {
PushDown(y);
t[y].son[0] = Merge(x, t[y].son[0]);
Update(y);
return y;
} else {
PushDown(x);
t[x].son[1] = Merge(t[x].son[1], y);
Update(x);
return x;
}
}
void Split(int rt, int pos, int &x, int &y) {
if (!rt)
x = y = 0;
else {
PushDown(rt);
if (pos <= t[t[rt].son[0]].tot) {
y = Clone(rt);
Split(t[y].son[0], pos, x, t[y].son[0]);
Update(y);
} else {
x = Clone(rt);
Split(t[x].son[1], pos - t[t[rt].son[0]].tot - 1, t[x].son[1], y);
Update(x);
}
}
}
void Insert(int &rt, int pos, int val) {
int x, y;
Split(rt, pos, x, y);
rt = Merge(Merge(x, NewNode(val)), y);
}
void Delete(int &rt, int pos) {
int x, y, z;
Split(rt, pos, x, z);
Split(x, pos - 1, x, y);
q.push(y);
rt = Merge(x, z);
}
void Reverse(int &rt, int l, int r) {
int x, y, z;
Split(rt, r, x, z);
Split(x, l - 1, x, y);
t[y].rev ^= 1;
rt = Merge(Merge(x, y), z);
}
ll Query(int &rt, int l, int r) {
int x, y, z;
Split(rt, r, x, z);
Split(x, l - 1, x, y);
ll res = t[y].sum;
rt = Merge(Merge(x, y), z);
return res;
}
int main() {
ll lat = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int v, opt;
scanf("%d %d", &v, &opt);
root[i] = root[v];
if (opt == 1) {
int pos, x;
scanf("%d %d", &pos, &x);
pos ^= lat; x ^= lat;
Insert(root[i], pos, x);
} else if (opt == 2) {
int pos;
scanf("%d", &pos);
pos ^= lat;
Delete(root[i], pos);
} else if (opt == 3) {
int x, y;
scanf("%d %d", &x, &y);
x ^= lat; y ^= lat;
Reverse(root[i], x, y);
} else {
int x, y;
scanf("%d %d", &x, &y);
x ^= lat; y ^= lat;
lat = Query(root[i], x, y);
printf("%lld\n", lat);
}
}
return 0;
}