【splay】hdu 1890

本文介绍了一种使用Splay树解决区间翻转问题的方法。通过构建Splay树并维护节点信息,实现每次翻转后第i位数都是有序的目标。文章详细解释了Splay树的操作流程及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

又抓了一道经典例题的splay。

这个主要是区间翻转,其中有一点树型dp的感觉。

传送门:Robotic Sort

题目大意:
每次拿第i位数和第i大的数之间的数进行翻转,这样每次翻转完毕之后第i位数一定是有序的。
每次输出第i大的数的位置,打印完毕后再进行翻转。


思路:
拿一棵splay数。按顺序插入数。
对于每个位置。维护他的序列号和值。
还要维护一个子树的最小值的位置。这个在每次操作的时候push_up一下就好了。
然后每次将第i - 1位旋转到root
将第i大的位的下一位旋转到root的右儿子结点
那么root的右儿子结点的左儿子树就是我们要进行翻转的区间。且这个子树的大小+i - 1就是我们要的答案。

splay最重要的是。创点的时候,别忘了创一个最小点和一个最大点。因为这样做才能使最坏需要操作整个区间的时候,这个区间能在root的右儿子的左子树下。

/*
@resources:
@date: 2017-09-02
@author: QuanQqqqq
@algorithm: splay
*/
#include <stdio.h>
#include <algorithm>
#include <string.h>

#define ll long long

using namespace std;

int n;

int num[350010][2];

struct SplayTree {
    const static int MAX_SIZE = 3e5 + 10;
    const static int INF = 0x7ffffff;
    int tree[MAX_SIZE][2], father[MAX_SIZE];  //该点的左右儿子,该点的爸爸节点,
    int size[MAX_SIZE];                      //该点的子节点数
    int val[MAX_SIZE][2];                       //每个点的价值
    int root, sz;                            //该树的根,该树的节点数
    int cnt;                                 //中序遍历指针
    int rev[MAX_SIZE];                       //翻转区间,懒惰更新
    int sum[MAX_SIZE];                       //求子树的和
    int lazy[MAX_SIZE];                      //求和,懒惰更新
    int mn[MAX_SIZE];                        //记录最小值

    void Treaval(int x) {
        if (x) {
            Treaval(tree[x][0]);
            printf("结点%2d:左儿子 %2d 右儿子 %2d 父结点 %2d size = %2d ,key = %2d %2d,sum = %2d\n", x, tree[x][0], tree[x][1], father[x], size[x], val[x][0], val[x][1], sum[x]);
            Treaval(tree[x][1]);
        }
    }

    void debug() {
        printf("%d\n",root);
        Treaval(root);
    }

    void modify(int x) {
        if (x) {
            rev[x] ^= 1;
            int tmp = tree[x][0];
            tree[x][0] = tree[x][1];
            tree[x][1] = tmp;
        }
    }

    bool is_small(int a, int b) {
        if (val[a][0] < val[b][0]) {
            return true;
        } else if (val[a][0] == val[b][0] && val[a][1] < val[b][1]) {
            return true;
        }
        return false;
    }

    //更新节点
    void push_up(int r) {
        size[r] = size[tree[r][0]] + size[tree[r][1]] + 1;
        /*判断点*/
        mn[r] = r;
        if (tree[r][0] && is_small(mn[tree[r][0]], mn[r])) {
            mn[r] = mn[tree[r][0]];
        }
        if (tree[r][1] && is_small(mn[tree[r][1]], mn[r])) {
            mn[r] = mn[tree[r][1]];
        }
    }

    //lazy更新
    void push_down(int r) {
        if (rev[r]) {   //翻转
            modify(tree[r][0]);
            modify(tree[r][1]);
            rev[r] = false;
        }
    }

    void new_node(int &r, int fa, int v) {
        r = ++sz;
        father[r] = fa;
        val[r][0] = num[v][0];
        val[r][1] = num[v][1];
        size[r] = 1;
        mn[r] = r;
        rev[r] = tree[r][0] = tree[r][1] = 0;
    }

    void build(int &rt, int l, int r, int fa) {   //建一棵满的二叉平衡树
        if (l > r) {
            return ;
        }
        int mid = l + r >> 1;
        new_node(rt, fa, mid);
        build(tree[rt][0], l, mid - 1, rt);
        build(tree[rt][1], mid + 1, r, rt);
        push_up(rt);
    }

    void clear() {
        sz = root = cnt = 0;
        num[0][0] = num[0][1] = size[root] = tree[0][0] = tree[0][1] = rev[0] = father[0] = sum[0] = lazy[0] = 0;
        memset(rev, 0, sizeof(rev));
        new_node(root, 0, 0);
        build(tree[root][1], 1, n + 1, root);  //建一棵在n+1左边的完全二叉平衡树
        push_up(root);
    }

    void rotate(int x, int k) {   //x是节点,k判断是左旋还是右旋
        int y = father[x];
        push_down(y);
        push_down(x);
        tree[y][!k] = tree[x][k]; //先变两边的儿子
        father[tree[x][k]] = y;     //再将x的儿子的爸爸变成y
        if (father[y]) {          //如果不是root,将y爸爸的儿子也变一下
            tree[father[y]][tree[father[y]][1] == y] = x;
        }
        father[x] = father[y];    //更新x的父亲成y的父亲
        father[y] = x;            //更新y的父亲成x
        tree[x][k] = y;           //更新x的儿子成y
        push_up(y);
    }

    void splay(int x, int r) {    //将x旋转到r的儿子,这里注意是儿子!
        if (r == 0) {
            root = x;
        }
        push_down(x);
        while (father[x] != r) {
            if (father[father[x]] == r) { //zig-step
                rotate(x, tree[father[x]][0] == x);
                return ;
            }
            int y = father[x];
            int k = tree[father[y]][0] == y;  //判断上上层是否是左边
            if (tree[y][k] == x) { // zig-zag-step
                rotate(x, !k);
                rotate(x, k);
            } else {                      // zig-zig-step
                rotate(y, k);
                rotate(x, k);
            }
        }
        push_up(x);
    }

    int get_next(int x) {  //获得比x下标大一点的值 
        push_down(x);
        int rg = tree[x][1];
        push_down(rg);
        if (rg == 0) {
            return father[x];
        }
        while (tree[rg][0]) {
            rg = tree[rg][0];
            push_down(rg);
        }
        return rg;
    } 
};

SplayTree spl;

int main() {
    while (scanf("%d", &n) && n) {
        for (int i = 1; i <= n; i++) {
            scanf("%d", &num[i][0]);
            num[i][1] = i;
        }
        num[n + 1][0] = spl.INF;
        num[n + 1][1] = spl.INF;
        spl.clear();
        int mnode = 1, snode;
        for (int i = 0; i < n - 1; i++) {
            spl.splay(mnode, 0);
            mnode = spl.mn[spl.tree[spl.root][1]]; //找到右边最小值 
            spl.splay(mnode, spl.root);            //这里splay操作一下保证有next,否则TLE 
            snode = spl.get_next(mnode);           //找到最小值的下一个位置 
            spl.splay(snode, spl.root);            //旋转到根的右儿子结点 
            printf("%d ", i + spl.size[spl.tree[snode][0]]);  //该点的左儿子节点即为区间里的数 
            spl.modify(spl.tree[snode][0]);        //翻转一下 
        }
        printf("%d\n", n);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值