【Splay 模板题】HDU 3587 BZOJ 1251

Step1 Problem:

HDU 3587
输入给出长 n 的一个序列,进行m次操作
1. 把序列中的第 a 个数字到第 b 个数字取出来插入到第 c 个数字后面
2. 翻转序列的 第 a 个数字到第 b 个数字这个区间
所有操作后输出结果
n <= 1e5 , m<=1e5

BZOJ 1251
输入给出长 n 的一个序列,进行m次操作
1. 把 [L, R] 这个区间的数都加上 V
2. 把 [L, R] 这个区间翻转
3. 求 [L, R] 这个区间的数的最值
n < 5e4,m<1e5

Step2 Ideas:

金桔的板子,东庆的注释,我就改了一点细微的格式,比较适合自己。

Step3 Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+10;
//用来处理区间询问,每个节点维护的是一个子树的信息
//Splay可以将一段连续区间内的节点放到一颗子树内,所以这样可以维护一段区间的信息
struct Info {
    int siz;
    int ma;
    Info(){};
    Info(int x) {
        siz = 1;
        ma = x;
    }
    void addIt(int x) {
        ma += x;
    }
};
//区间(子树)信息的合并
Info operator+(const Info &l, const Info &r)
{
    Info ret;
    ret.siz = l.siz + r.siz;
    ret.ma = max(l.ma, r.ma);
    return ret;
}
int root;
//Splay的节点
struct node {
    //son[0]是左儿子,son[1]是右儿子
    int son[2], fa;
    int val, lazy;
    bool flp;
    Info info;
    int &l() {return son[0];}
    int &r() {return son[1];}
    node(int v = 0) {
        l() = r() = fa = -1;
        val = v;
        info = Info(v);
        lazy = 0;
        flp = false;
    }
    //修改Splay上的节点后,也需要对 info 的信息进行维护
    void addIt(int v) {
        val += v;
        lazy += v;
        info.addIt(v);
    }
    void maintain();
    void push_down();
}tree[N];
//进行 pushdown 操作,类似线段树
void node::push_down() {
    if(lazy != 0) {
        if(l() != -1) tree[l()].addIt(lazy);
        if(r() != -1) tree[r()].addIt(lazy);
        lazy = 0;
    }
    if(flp) {
        swap(l(), r());
        if(l() != -1) tree[l()].flp ^= 1;
        if(r() != -1) tree[r()].flp ^= 1;
        flp = false;
    }
}
//Splay 进行旋转操作时,子树发生了改变,需要重新维护区间(子树)信息
void node::maintain() {
    info = Info(val);
    if(l() != -1) info = tree[l()].info + info;
    if(r() != -1) info = info + tree[r()].info;
}
//查询当前节点是父亲的左儿子还是右儿子,左儿子返回0,右儿子返回1,如果无父亲返回-1
int ori(int st) {
    int fa = tree[st].fa;
    if(fa == -1) return -1;
    return st == tree[fa].r();
}
//把 sn 变成 st 的儿子节点,如果 d 是 0 是左儿子,否则是右儿子
//这里子树发生了改变,需要重新维护 info 信息
void setc(int sn, int st, int d) {
    if(st != -1) {
        tree[st].son[d] = sn;
        tree[st].maintain();
    }
    if(sn != -1) tree[sn].fa = st;
}
//进行旋转操作,这里需要自己画图理解一下,这里包括了左旋和右旋
void zg(int x) {
    int st = tree[x].fa, p = -1;
    tree[st].push_down();
    tree[x].push_down();
    int d = ori(x), dst = ori(st);
    if(st != -1) p = tree[st].fa;
    setc(tree[x].son[d^1], st, d);
    setc(st, x, d^1);
    setc(x, p, dst);
}
#define f(x) (tree[x].fa)
//将 x 旋转成 fa 的儿子,如果将 x 旋转成 根节点的话则不填 fa
void splay(int x, int fa = -1) {
    //循环直到 x 是 fa 的儿子
    while(f(x) != fa) {
        //如果 fa 是 x 的爷爷,那么只需要一次旋转
        if(f(f(x)) == fa) zg(x);
        else {
            //双旋!
            //说明进行 zig zig 或者 zag zag 旋转
            if(ori(x) == ori(f(x))) zg(f(x));
            else zg(x);
            zg(x);
        }
    }
    //更新根节点
    if(fa == -1) root = x;
}
int value[N];
int pos;
//要保证 value 有序,类似线段树建树,这样树高是 log(n) 的
int build(int l, int r) {
    int st = pos++;
    int m = (l+r)>>1;
    tree[st] = node(value[m]);
    if(l < m) setc(build(l, m-1), st, 0);
    if(r > m) setc(build(m+1, r), st, 1);
    return st;
}
int build(int n) {
    pos = 0;
    //添加 0 和 n + 1 两个虚拟节点,方便 cut 操作
    return build(0, n+1);
}
//获得以 st 为根节点,中序遍历的第 v 个节点
int getid(int v, int st) {
    tree[st].push_down();
    int l = tree[st].l();
    int lsiz = 1 + (l == -1 ? 0 : tree[l].info.siz);
    if(v == lsiz) return st;
    int d = v > lsiz;
    if(d) v -= lsiz;
    return getid(v, tree[st].son[d]);
}
int getseg(int l, int r) {
    l--, r++;
    //下标从 0 开始,需要找第 l + 1 个节点
    l = getid(l+1, root), r = getid(r+1, root);
    //现在 r+1 是 l-1 的父亲,那么 l-r 这一段子树肯定是 l-1 的右儿子
    splay(r);
    splay(l, r);
    return tree[l].r();
}
void flip(int l, int r) {
    int pos = getseg(l, r);
    tree[pos].flp ^= 1;
}
void cut(int l, int r, int idx) {
    //切下来 l - r 这段区间
    int rootson1 = getseg(l, r);
    int father = tree[rootson1].fa;
    setc(-1, father, 1);
    l = idx, r = idx+1;
    //因为这里是虚拟节点,所以要多加一个 1
    l = getid(l+1, root);
    r = getid(r+1, root);
    //将 idx+1 成为 idx 的父亲,那么上面切下来的区间放到idx的右边即可
    splay(r);
    splay(l, r);
    setc(rootson1, l, 1);
}
int n, m;
int ans[N], cnt;
//中序遍历
void dfs(int u)
{
    tree[u].push_down();
    if(tree[u].son[0] != -1) dfs(tree[u].son[0]);
    ans[cnt++] = tree[u].val;
    if(tree[u].son[1] != -1) dfs(tree[u].son[1]);
}
int main()
{
    char op[10];
    while(~scanf("%d %d", &n, &m))
    {
        if(n==-1 && m ==-1) break;
        for(int i = 1; i <= n; i++) value[i] = i;
        root = build(n);
        int l, r, idx;
        while(m--)
        {
            scanf("%s", op);
            if(op[0] == 'C') {
                scanf("%d %d %d", &l, &r, &idx);
                cut(l, r, idx);
            }
            else {
                scanf("%d %d", &l, &r);
                flip(l, r);
            }
        }
        cnt = 0;
        dfs(root);
        for(int i = 1; i < cnt-1; i++)
        {
            if(i > 1) printf(" ");
            printf("%d", ans[i]);
        }
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值