fhq treap 难点在于理解merge函数和split函数,这些通过手算模拟可以很好地抓住要点,其次就是要理解pushdown函数里面的交换结点,正是这个动作转换了区间,还有比较难理解的就是按照merge函数的建树(小根堆)规则可以使树的中序遍历刚刚好就是原序列。
fhq treap:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
using namespace std;
const int M = 1e3 + 10;
int n, m, root, l, r, p, cnt;
struct node {
int l, r, size, val, lazy, key;
} tree[M];
void adde(int x) { //建树,由于它是依次增加的,所以可以用x,不需要cnt
tree[x].val = x;
tree[x].size = 1;
tree[x].key = rand();
tree[x].l = tree[x].r = 0;
}
void pushdown(int u) { //下传懒标记,注意先交换,再下传,原因自己悟
swap(tree[u].l, tree[u].r); //交换节点
//下传懒标记
tree[tree[u].l].lazy ^= 1;
tree[tree[u].r].lazy ^= 1;
tree[u].lazy = 0; //清除标记
}
void update(int u) { //更新操作
tree[u].size = tree[tree[u].l].size + tree[tree[u].r].size + 1;
}
void split(int u, int x, int& l, int& r) {
if (!u) {
l = r = 0;
return;
}
if (tree[u].lazy) //处理当时的懒标记
pushdown(u); //下传懒标记
//按照区间分割
if (tree[tree[u].l].size + 1 <= x) { //确定左儿子
l = u;
split(tree[u].r, x - tree[tree[u].l].size - 1, tree[u].r,
r); //!注意右儿子的此时的size满足的值需要减去左儿子的个数
} else { //确定右儿子
r = u;
split(tree[u].l, x, l,
tree[u]
.l); //!注意左儿子此时的size不需要减去,理由建范浩强平衡树模板
}
update(u); //同模板一样,更新size的值
}
int merge(int l, int r) { //合并操作
if (!l || !r)
return l + r;
//按照键值维护一个小根堆
if (tree[l].key < tree[r].key) { // l当父节点
if (tree[l].lazy) //下传懒标记
pushdown(l);
tree[l].r = merge(tree[l].r, r); //确定l的右儿子
update(l);
return l;
} else { // r当父节点
if (tree[r].lazy) //下传懒标记
pushdown(r);
tree[r].l = merge(l, tree[r].l); //确定r的左儿子
update(r);
return r;
}
}
void print(int u) { //按照中序遍历
if (tree[u].lazy) //如果还存在懒标记则下传
pushdown(u);
if (tree[u].l) //先遍历左儿子
print(tree[u].l);
printf("%d ", tree[u].val); //再遍历父节点
if (tree[u].r) //最后遍历右儿子
print(tree[u].r);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
adde(i); //建立新的边
root = merge(root, i); //合并更新根节点
}
for (int i = 1, x, y; i <= m; i++) {
scanf("%d%d", &x, &y);
//分成分别以l,p,r为根节点的三棵树
split(root, y, l, r);
split(l, x - 1, l, p);
tree[p].lazy ^=
1; //更新懒标记,每次的p值会发生改变,只有相同的区间p值才会不同,不用担心
//(:^_^:)
root = merge(merge(l, p), r); //合并
}
print(root); //输出
return 0;
}
treap:
#include <bits/stdc++.h>
#define max(x, y) ((x) > (y) ? (x) : (y))
#define min(x, y) ((x) < (y) ? (x) : (y))
#define LL long long
#define swap(x, y) (x ^= y, y ^= x, x ^= y)
#define tc() \
(A == B && (B = (A = ff) + fread(ff, 1, 100000, stdin), A == B) ? EOF \
: *A++)
#define pc(ch) \
(pp_ < 100000 ? pp[pp_++] = (ch) \
: (fwrite(pp, 1, 100000, stdout), pp[(pp_ = 0)++] = (ch)))
#define N 100000
int pp_ = 0;
char ff[100000], *A = ff, *B = ff, pp[100000];
using namespace std;
int n, m, rt;
struct splay {
int Son[2], Size, Father, flag;
} node[N + 5];
inline void read(int& x) {
x = 0;
int f = 1;
char ch;
while (!isdigit(ch = tc()))
f = ch ^ '-' ? 1 : -1;
while (x = (x << 3) + (x << 1) + ch - '0', isdigit(ch = tc()))
;
x *= f;
}
inline void write(int x) {
if (x < 0)
pc('-'), x = -x;
if (x > 9)
write(x / 10);
pc(x % 10 + '0');
}
inline void PushUp(int x) { //更新x结点的size大小
node[x].Size = node[node[x].Son[0]].Size + node[node[x].Son[1]].Size + 1;
}
inline void PushDown(int x) //下推翻转标记
{
if (node[x].flag)
swap(node[x].Son[0], node[x].Son[1]), node[node[x].Son[0]].flag ^= 1,
node[node[x].Son[1]].flag ^= 1,
node[x].flag =
0; //如果当前节点有翻转标记,那么交换其左右儿子,更新其左右儿子的翻转标记,然后清空当前节点的翻转标记
}
inline void Build(int l, int r, int& x) //一个建树的过程,是不是很像线段树?
{
node[x = l + r >> 1].Size = 1; //先记录当前节点的编号和子树大小
if (l < x)
Build(l, x - 1, node[x].Son[0]),
node[node[x].Son[0]].Father =
x; //如果当前节点左边还有元素,那么就继续对其左儿子建树
if (x < r)
Build(x + 1, r, node[x].Son[1]),
node[node[x].Son[1]].Father =
x; //如果当前节点右边还有元素,那么就继续对其右儿子建树
PushUp(x); //更新节点信息
}
inline int Which(int x) //判断当前节点是父亲的哪一个儿子
{
return node[node[x].Father].Son[1] == x;
}
inline void Rotate(int x, int& k) //旋转操作
{
int fa = node[x].Father, grandpa = node[fa].Father, d = Which(x);
if (fa ^ k)
node[grandpa].Son[Which(fa)] = x;
else
k = x;
node[x].Father = grandpa, node[fa].Son[d] = node[x].Son[d ^ 1],
node[node[x].Son[d ^ 1]].Father = fa, node[x].Son[d ^ 1] = fa,
node[fa].Father = x, PushUp(fa), PushUp(x);
}
inline void Splay(int x, int& k) //不断将一个元素旋转至目标位置
{
for (int fa = node[x].Father; x ^ k; fa = node[x].Father) {
if (fa ^ k)
Rotate(Which(fa) ^ Which(x) ? x : fa, k);
Rotate(x, k);
}
}
inline int get_val(int pos) //求出中序遍历到的顺序为pos的节点的值
{
int x = rt;
while (x) {
PushDown(x); //先下推标记,然后再操作
if (node[node[x].Son[0]].Size == pos)
return x; //如果当前节点中序遍历到的顺序等于pos,就返回当前节点的值
if (node[node[x].Son[0]].Size > pos)
x = node[x].Son
[0]; //如果当前节点左子树被中序遍历到的顺序大于pos,就访问当前节点的左子树
else
pos -= node[node[x].Son[0]].Size + 1,
x = node[x].Son[1]; //否则,更新pos,访问右子树
}
}
inline void rever(int x, int y) //翻转一个区间,具体操作见上面的解析
{
int l = get_val(x - 1), r = get_val(y + 1);
Splay(l, rt), Splay(r, node[rt].Son[1]),
node[node[node[rt].Son[1]].Son[0]].flag ^= 1;
}
int main() {
register int i;
int x, y;
for (read(n), Build(1, n + 2, rt), read(m); m; --m)
read(x), read(y), rever(x, y);
for (i = 1; i <= n; ++i)
write(get_val(i) - 1),
pc(' '); //由于我们用中序遍历到的顺序为2~n+1的节点来表示序列中第1~n个元素,所以输出时将答案减1
return fwrite(pp, 1, pp_, stdout), 0;
}