伸展树基本概念基本题目

http://blog.csdn.net/discreeter/article/details/51524210   //基本概念详见这里

例题HDU4453 代码来源http://blog.csdn.net/auto_ac/article/details/12318809

伸展树我个人理解就是每次查询或更改都要将其移动至根节点 另外伸展树有单点操作和区间操作 维护的是一个中序遍历(这点很重要)

旋转操作的话结合概念和代码还是很清晰的,有左旋(当要进行旋转操作的是其根节点的右节点),右旋(当要进行旋转操作的是其根节点的左节点),还有一字型旋转和之字形旋转,详见基本概念

情况一:节点x的父节点y是根节点。这时,如果x是y的左孩子,我们进行一次Zig(右旋)操作;如果x 是y 的右孩子,则我们进行一次Zag(左旋)操作。经过旋转,x成为二叉查找树S的根节点,调整结束。即:如果当前结点父结点即为根结点,那么我们只需要进行一次简单旋转即可完成任务,我们称这种旋转为单旋转。如图1所示

(图1)

情况二:节点x 的父节点y 不是根节点,y 的父节点为z,且x 与y 同时是各自父节点的左孩子或者同时是各自父节点的右孩子。这时,我们进行一次Zig-Zig操作或者Zag-Zag操作。即:设当前结点为X , X 的父结点为Y ,Y 的父结点为Z ,如果Y 和X 同为其父亲的左孩子或右孩子,那么我们先旋转Y ,再旋转X 。我们称这种旋转为一字形旋转。如图2所示

(图2)

情况三:节点x的父节点y不是根节点,y的父节点为z,x与y中一个是其父节点的左孩子而另一个是其父节点的右孩子。这时,我们进行一次Zig-Zag操作或者Zag-Zig 操作。即:这时我们连续旋转两次X 。我们称这种旋转为之字形旋转。如图3所示

(图3)

如图4所示,执行Splay(1,S),我们将元素1 调整到了伸展树S 的根部。再执行Splay(2,S),如图5 所示,我们从直观上可以看出在经过调整后,伸展树比原来“平衡”了许多。而伸展操作的过程并不复杂,只需要根据情况进行旋转就可以了,而三种旋转都是由基本得左旋和右旋组成的,实现较为简单。

(图4)

(图5)

区间操作的图解原网站也很清晰:

首先我们认为伸展树的中序遍历即为我们维护的数列,那么很重要的一个操作就是怎么在伸展树中表示任意一个区间。比如我们要提取区间a,b],那么我们将a前面一个数对应的结点转到树根,将b 后面一个结点对应的结点转到树根的右边,那么根右边的左子树就对应了区间[a,b]。其中的道理也是很简单的,将a 前面一个数对应的结点转到树根后, a 及a 后面的数就在根的右子树上,然后又将b后面一个结点对应的结点转到树根的右边,那么[a,b]这个区间就是图8中*所示的子树。

利用这个,我们就可以实现线段树的一些功能,比如回答对区间的询问。我们在每个结点上记录关于以这个结点为根的子树的信息,然后询问时先提取区间,再直接读取子树的相关信息。还可以对区间进行整体修改,这也要用到和线段树类似的延迟标记技术,就是对于每个结点,再额外记录一个或多个标记,表示以这个结点为根的子树是否被进行了某种操作,并且这种操作影响其子结点的信息值。当然,既然记录了标记,那么旋转和其他一些操作中也就要相应地将标记向下传递。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define KT ch[ ch[root][1] ][0]
#define L ch[x][0]
#define R ch[x][1]
const int maxn = 300005;
int num[maxn], n, m;
int k1, k2;
typedef long long ll;
struct splayTree {
    int ch[maxn][2], sz[maxn], pre[maxn];
    int root, tot, all; // all:节点总数, tot:最大标号

int add[maxn], val[maxn];
    bool flip[maxn]; //翻转标记
    int sta[maxn], top;
    void rotate(int &x, int f) {//f等于一是右旋
           int y = pre[x], z = pre[y];
           down(y); down(x);
           ch[y][!f] = ch[x][f];
           pre[ch[x][f]] = y;
           pre[x] = pre[y];
           if(z) ch[z][ch[z][1] == y] = x;
           ch[x][f] = y;
           pre[y] = x;
           up(y);
       }
       void splay(int &x, int g) {
           down(x);
           while(pre[x] != g) {
               int y = pre[x], z = pre[y];
               down(z); down(y); down(x);
               if(z == g) {
                   rotate(x, ch[y][0] == x);
               }
               else {
                   int f = (ch[z][0] == y);
                   ch[y][!f] == x ? rotate(y, f) : rotate(x, !f);
                   rotate(x, f);
               }
           }
           if(!g) root = x;
           up(x);
       }
       void rto(int k, int g) {
           int x = root;
           while(1) {
               down(x);

if(sz[L] == k) break;
               if(sz[L] > k) x = L;
               else {
                   k -= sz[L] + 1;
                   x = R;
               }
           }
           splay(x, g);
       }
    void down(int x){
        if(add[x]) {
            val[L] += add[x];
            val[R] += add[x];
            add[L] += add[x];
            add[R] += add[x];
            add[x] = 0;
        }
        if(flip[x]) {
            flip[L] ^= 1;
            flip[R] ^= 1;
            swap(L, R);
            flip[x] = 0;
        }
    }
    void up(int x) {
        sz[x] = sz[L] + sz[R] +1;
    }

void build(int &x, int l, int r, int fa) {
        if(l > r) return;
        int m = (l+r) >> 1;
        newNode(x, num[m], fa);
        build(L, l, m-1, x);
        build(R, m+1, r, x);
        up(x);
    }
    void newNode(int &x, int v, int fa) {
        if(top) x = sta[top--]; //内存回收
        else x = ++tot;
        all++;
        pre[x] = fa;
        sz[x] = 1;
        L = R = 0;
        val[x] = v;
        add[x] = 0;
        flip[x] = 0;
    }
    void init(int n) {
        all = tot = top = 0;
        newNode(root, 0, 0);
        // all:节点总数, tot:最大标号
        newNode(ch[root][1], 0, root);
        for(int i = 0; i < n; i++)
            scanf("%d", &num[i]);
        build(KT, 0, n-1, ch[root][1]);
        up(ch[root][1]);
        up(root);
        debug();
    }
    ll erase(int k) { //删除第k个数
        rto(k, 0);
        rto(k-1, root);
        sta[++top] = root;
        all--;
        ll ret = val[root];
        int ls = ch[root][0], rs = ch[root][1];
        root = ls;
        pre[ls] = 0;
        ch[ls][1] = rs;
        if(rs)pre[rs] = ls;
        up(root);
        return ret;
    }
    void insert(int k, int v) { //在第k个数后面插入一个数v
        rto(k, 0);
        int x;
        int rs = ch[root][1];
        newNode(x, v, root);
        ch[root][1] = x;
        ch[x][1] = rs;
        if(rs) pre[ch[x][1]] = x;
        up(ch[root][1]);
        up(root);
    }
    void move1() {
        int v = erase(all-2);
        insert(0, v);
    }
    void move2() {
        int v = erase(1);
        insert(all-2, v);

}
    void update(int l, int r, int v) {
        rto(l-1, 0);
        debug();
        rto(r+1, root);
        debug();
        val[KT] += v;
        add[KT] += v;
        up(ch[root][1]);
        up(root);
    }
    void reverse(int l, int r) {
        rto(l-1, 0);
        debug();
        rto(r+1, root);
        debug();
        flip[KT] ^= 1;
        up(ch[root][1]);
        up(root);
    }
    int query() {
        rto(1, 0);
        return val[root];
    }
    void print(int x) {
        printf("node %d: ls=%d rs=%d lsz = %d rsz = %d val = %d\n", x, L, R, sz[L], sz[R], val[x]);
        if(L)print(L);
        if(R)print(R);
    }
    void debug() {
        printf("root = %d\n", root);
         print(root);
    }
}spt;
int main() {
    int ca = 1;
    while(~scanf("%d%d%d%d", &n, &m, &k1, &k2)) {
        if(!m && !n && !k1 && !k2) break;
        char op[111];
        spt.init(n);
      //spt.debug();
        printf("Case #%d:\n", ca++);
        while(m--) {
            scanf("%s", op);
            if(op[0] == ‘q‘) {
                printf("%d\n", spt.query());
                spt.debug();
            }
            if(op[0] == ‘m‘) {
                int kd;
                scanf("%d", &kd);
                if(kd == 1) spt.move1();
                else spt.move2();
                spt.debug();
            }
            if(op[0] == ‘i‘) {
                int v;
                scanf("%d", &v);
                spt.insert(1, v);
                spt.debug();
            }
            if(op[0] == ‘d‘)
                {spt.erase(1);spt.debug();}
            if(op[0] == ‘a‘) {
                int v;
                scanf("%d", &v);
                spt.update(1, k2, v);
                spt.debug();
            }
            if(op[0] == ‘r‘)
                spt.reverse(1, k1);
        }
    }
    return 0;
}

时间: 2025-01-02 01:20:38

伸展树基本概念基本题目的相关文章

PHP算法 《树形结构》 之 伸展树(1) - 基本概念

伸展树的介绍 1.出处:http://www.cnblogs.com/skywang12345/p/3604238.html 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造.(01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x].如果y是x的左子树中的一个结点,则key[y] <= key

poj_3468 伸展树

题目大意 一个数列,每次操作可以是将某区间数字都加上一个相同的整数,也可以是询问一个区间中所有数字的和.(这里区间指的是数列中连续的若干个数)对每次询问给出结果. 思路 1. 伸展树的一般规律 对于区间的查找更新操作,可以考虑使用伸展树.线段树等数据结构.这里使用伸展树来解决.     伸展树对数组进行维护的核心思想是,将需要维护的一组数单独提取出来,形成一棵子树(一般为整棵树的根节点的右子节点的左孩子节点 为根),然后再这个子树上进行操作.此时进行某些操作(如 ADD, SUM 等),只需要在

HYSBZ 1503 郁闷的出纳员 伸展树

题目链接: https://vjudge.net/problem/26193/origin 题目描述: 中文题面....... 解题思路: 伸展树, 需要伸展树的模板, 突然发现自己昨天看到的模板不是太好, 现在又新找了一个,  很简练, 自己将模板的实现从头到尾看了一遍, 觉得数组实现的实在是非常的巧妙, 然后自己照着敲了一遍, 边敲边看, 崩掉了.....肯定是哪里手残了, 没有必要浪费时间去改了, 有那时间不如看点别的 代码: #include <iostream> #include &

uva 11922 - Permutation Transformer(伸展树)

题目链接:uva 11922 - Permutation Transformer 题目大意:给定一个序列,每次操作取出区间a~b,翻转后放到末尾,随后输出序列. 解题思路:就是伸展树,对于每个节点设一个flip,表示是否为翻转转态.每次将a旋转到根,然后分裂,再将b翻转到根,分裂,然后将mid翻转放到最后. #include <cstdio> #include <cstring> #include <algorithm> using namespace std; str

poj_3580 伸展树

自己伸展树做的第一个题 poj 3580 supermemo. 题目大意 对一个数组进行维护,包含如下几个操作: ADD x, y, d 在 A[x]--A[y] 中的每个数都增加d REVERSE x, y 将 A[x]--A[y] 中的数进行反转,变为 A[y],A[y-1]....A[x+1],A[x] REVOLVE x, y, T 将 A[x]--A[y]中的数连续右移T次 INSERT x, P 在A后添加数P DELETE x 删除A[x] MIN x, y 查询A[x]--A[y

POJ 3580 (伸展树)

题目链接: http://poj.org/problem?id=3580 题目大意:对一个序列进行以下六种操作.输出MIN操作的结果. 解题思路: 六个操作,完美诠释了伸展树有多么吊.注意,默认使用Lazy标记,在pushdown中维护. ADD操作:为x~y元素加一个d值.首先用split切出x~y元素.然后改变给切出的root->add,root->min,root->v.再merge进原序列. REVERSE操作:把x~y元素反转.首先用split切出x~y元素,然后改变root-

Codeforces 38G Queue 伸展树

题目链接:点击打开链接 题意: 给定n个人来排队 每个人有2个参数,身份优先级和脸皮厚度 == 来的那个人会排到队尾 如果这个人的优先级比他前面那个人的优先级大就会和前面那个人交换位置. 交换一次脸皮厚度减1, 一直交换到队头或者脸皮厚度为0 交换完成后下一个人才会到来. 问: 队伍最后的情况(从队头到队尾依次输出每个人的编号) 思路:splay 维护子树的最小值. 插入时递归插入,若当前点是空就插这个位置. 然后就是裸的splay.. == #include <stdio.h> #inclu

树-伸展树(Splay Tree)

伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x].如果y是x的左子树中的一个结点,则key[y] <= key[x]:如果y是x的右子树的一个结点,则key[y] >= key[x]. (02) 除了拥有二叉查找树的性质

POJ 3580 SuperMemo(伸展树的基本操作)

题目大意:给你六个操作,让你实现这些功能. 解题思路:伸展树的基本应用,用伸展数实现各种功能. SuperMemo Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 10404   Accepted: 3320 Case Time Limit: 2000MS Description Your friend, Jackson is invited to a TV show called SuperMemo in which t