Uva 11922 Splay

Splay(伸展树)实现可分裂与合并的序列

对于BST,除了Treap树之外,还有一种Splay的伸展树,他能快速的分裂与合并。

重要的操作是伸展操作,将一个指定的结点 x 旋转到根的过程。

分三种情况,一次单旋,两次同向单旋,两次反向旋转。可以手动模拟一下这个过程。

到这里,问题常常是将序列的第 k 个元素旋转到根。

首先,要知道的是伸展树里面v存的是什么,是节点的编号(下标)。这样才能像 Treap实现名次树那样,很方便的找到左边第 k 个元素。

//将序列左数第k个元素选择到根
void splay(Node* &o,int k) {
    o->pushdown();
    int d = o->cmp(k);
    if(d==1) k-=o->ch[0]->s + 1;
    if(d!=-1) {
        Node* p = o->ch[d];
?
        p->pushdown();
        int d2 = p->cmp(k);
        int k2 = (d2==0 ? k : k - p->ch[0]->s - 1);
        if(d2!=-1) {
            splay(p->ch[d2],k2);
            if(d==d2) rotate(o,d^1);
            else rotate(o->ch[d],d);
        }
        rotate(o,d^1);
    }
}

分裂与合并:

分裂:从序列从左第 k 个元素分裂,就是将序列的 o 的第 K 小元素伸展到根,断开树根与右子节点。

合并:将left部分最大的元素旋转到根,将right作为 left的右子树。(保证right>left所有元素)。

// 合并操作。假定left所有元素小于 right
Node* merge(Node* left,Node* right) {
    splay(left,left->s);
    left->ch[1] = right;
    left->maintain();
    return left;
}
?
//把 o 前 k 个小结点放到left里面,其他放到ritht里面,如果不够right = null
void split(Node* o,int k,Node* &left,Node* &right) {
    splay(o,k);
    left = o;
    right = o->ch[1];
    o->ch[1] = null;
    left->maintain();
}

有时,对于序列有反转操作,这时,利用 线段树的 lazy标记,标记某一段是否反转。

对于,数据结构的定义:用一个Node数组,和一个Node 的 root指针,指向这个数组的元素。

#include <bits/stdc++.h>

using namespace std;

struct Node {
    Node *ch[2];
    int s;
    int flip;
    int v;
    int cmp(int k) const {
        int d = k - ch[0]->s;
        if(d==1) return -1;     //序列第 k 个找到
        return d <=0 ? 0 : 1;
    }

    void maintain() {
        s = ch[0]->s + ch[1]->s + 1;
    }

    void pushdown() {
        if(flip) {
            flip = 0;
            swap(ch[0],ch[1]);
            ch[0]->flip = !ch[0]->flip;
            ch[1]->flip = !ch[1]->flip;
        }
    }
};

Node *null = new Node();

// d = 0 左旋
void rotate(Node* &o,int d) {
    Node* k = o->ch[d^1];
    o->ch[d^1] = k->ch[d];
    k->ch[d] = o;
    o->maintain();
    k->maintain();
    o = k;
}

//将序列左数第k个元素选择到根
void splay(Node* &o,int k) {
    o->pushdown();
    int d = o->cmp(k);
    if(d==1) k-=o->ch[0]->s + 1;
    if(d!=-1) {
        Node* p = o->ch[d];

        p->pushdown();
        int d2 = p->cmp(k);
        int k2 = (d2==0 ? k : k - p->ch[0]->s - 1);
        if(d2!=-1) {
            splay(p->ch[d2],k2);
            if(d==d2) rotate(o,d^1);
            else rotate(o->ch[d],d);
        }
        rotate(o,d^1);
    }
}

// 合并操作。假定left所有元素小于 right
Node* merge(Node* left,Node* right) {
    splay(left,left->s);
    left->ch[1] = right;
    left->maintain();
    return left;
}

//把 o 前 k 个小结点放到left里面,其他放到ritht里面,如果不够right = null
void split(Node* o,int k,Node* &left,Node* &right) {
    splay(o,k);
    left = o;
    right = o->ch[1];
    o->ch[1] = null;
    left->maintain();
}

const int maxn = 1e5+10;
struct SplaySequence {
    int n;
    Node seq[maxn];
    Node *root;

    Node* build(int sz) {
        if(!sz) return null;
        Node* L = build(sz/2);
        Node* o = &seq[++n];
        o->v = n;
        o->ch[0] = L;
        o->ch[1] = build(sz-sz/2-1);
        o->flip = o ->s = 0;
        o->maintain();
        return o;
    }

    void init(int sz) {
        n = 0;
        null->s = 0;
        root = build(sz);
        for(int i = 0; i < sz; i++)
            printf("%d ",seq[i].v);
        puts("");
    }

};

vector<int> ans;
void print(Node* o) {
    if(o!=null) {
        o->pushdown();
        print(o->ch[0]);
        ans.push_back(o->v);
        print(o->ch[1]);
    }
}

void debug(Node* o) {
    if(o!=null) {
        o->pushdown();
        debug(o->ch[0]);
        printf("%d \n",o->v -1);
        debug(o->ch[1]);
    }
}

SplaySequence ss;

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    ss.init(n+1);

    debug(ss.root);

    for(int i = 0; i < m; i++) {
        int a,b;
        scanf("%d%d",&a,&b);
        Node* left,*mid,*right,*o;
        split(ss.root,a,left,o);
        split(o,b-a+1,mid,right);
        mid->flip ^=1;
        ss.root = merge(merge(left,right),mid);
    }

    print(ss.root);
    for(int i = 1; i < (int)ans.size(); i++)
        printf("%d\n",ans[i]-1);

    return 0;
}
时间: 2024-12-19 02:46:28

Uva 11922 Splay的相关文章

UVA 11922 Splay区间翻转+分裂+合并

- Permutation Transformer Time Limit:2000MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Submit Status Practice UVA 11922 Appoint description:  System Crawler  (2014-11-30) Description  Permutation Transformer  Write a program to transform th

UVa 11922 &amp; splay的合并与分裂

题意: 1个1—n的排列,实现一下操作:将a—b翻转并移动至序列的最后. SOL: splay维护区间的裸题——不过平衡树的题目貌似都是裸的吧...就是看操作的复杂程度罢... 如何取区间呢,我们在splay中新增两个头尾结点,我们暂且把他叫做卫兵好了,永远把序列夹在中间——注意这个永远,我们在进行序列操作时便要维护并利用这个永远. 取序列在那个splay的线段树中已经讲过了,我们分离出一棵子树即可,如何插入呢.我们注意到要将这个点插入序列尾,它一定在尾卫兵与分离后序列最末结点之间.那么我们只要

uva 11922 - Permutation Transformer(伸展树)

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

UVA 11922 Permutation Transformer —— splay伸展树

题意:根据m条指令改变排列1 2 3 4 - n ,每条指令(a, b)表示取出第a~b个元素,反转后添加到排列尾部 分析:用一个可分裂合并的序列来表示整个序列,截取一段可以用两次分裂一次合并实现,粘贴到末尾可以用一次合并实现. 翻转可以采用在每个结点上做标记的方法,flip = 1意味着将这棵子树翻转,可以类似线段树用一个pushdown()实现标记向下传递. 可以发现当前排列就是伸展树的中序遍历序列.中序遍历打印结果即可. 注意代码中设置了虚拟首结点0的技巧. 代码如下: 1 #includ

UVA 11922(Splay

题目:维护一个序列,支持将一段数翻转并插到最后的操作,最后输出整个序列. 思路:直接套的大白模板,第一次用splay,贴一下.. /* *@author: Cwind *http://www.cnblogs.com/Cw-trip/ */ #include <bits/stdc++.h> #define pb push_back #define PB pop_back #define bk back() #define se second #define fs first #define II

uva 11922 Permutation Transforme/splay tree

原题链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=18902 伸展树的区间翻转剪切... 如下: 1 #include<cstdio> 2 #include<cstdlib> 3 #include<iostream> 4 #include<algorithm> 5 const int Max_N = 100010; 6 struct Node{ 7 int v, s, rev;

Uva 11922 Permutation Transformer

闲的没事打了打一个splay,,,,, 注意只要是遍历数据结构就要下传标记!!!! 只要是修改就要维护所有受到影响的!!! 希望以后不要再犯这种zz错误了hhh #include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<cstdlib> #define ll long long #define m

UVA - 11922 区间反转+拼接 可持久化Treap

题意:一开始给出一个序列\(1,2...n\),然后\(m\)次操作,每次把\([l,r]\)翻转并且拼接到序列的后面,求最后形成的序列 打个pushdown标记就好 #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<string> #inc

11922 - Permutation Transformer (Splay区间翻转)

UVA 11922 - Permutation Transformer 题目链接 题意:给一个序列,每次操作选择(a,b),把(a,b)序列翻转之后加到末尾,求最终序列 思路:Splay的应用,多一个flip标记,在开头多一个虚拟的0结点,这样每次就利用Splay进行分裂合并即可 代码: #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include