P3391 【模板】文艺平衡树(模板题)(fhq treap)

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值