Splay树——HDU 3487 Play with Chain

对应HDU题目:点击打开链接

Play with Chain

Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)

Total Submission(s): 4571    Accepted Submission(s): 1859

Problem Description

YaoYao is fond of playing his chains. He has a chain containing n diamonds on it. Diamonds are numbered from 1 to n.

At first, the diamonds on the chain is a sequence: 1, 2, 3, …, n.

He will perform two types of operations:

CUT a b c: He will first cut down the chain from the ath diamond to the bth diamond. And then insert it after the cth diamond on the remaining chain.

For example, if n=8, the chain is: 1 2 3 4 5 6 7 8; We perform “CUT 3 5 4”, Then we first cut down 3 4 5, and the remaining chain would be: 1 2 6 7 8. Then we insert “3 4 5” into the chain before 5th diamond, the chain turns out to be: 1 2 6 7 3 4 5 8.

FLIP a b: We first cut down the chain from the ath diamond to the bth diamond. Then reverse the chain and put them back to the original position.

For example, if we perform “FLIP 2 6” on the chain: 1 2 6 7 3 4 5 8. The chain will turn out to be: 1 4 3 7 6 2 5 8

He wants to know what the chain looks like after perform m operations. Could you help him?

Input

There will be multiple test cases in a test data.

For each test case, the first line contains two numbers: n and m (1≤n, m≤3*100000), indicating the total number of diamonds on the chain and the number of operations respectively.

Then m lines follow, each line contains one operation. The command is like this:

CUT a b c // Means a CUT operation, 1 ≤ a ≤ b ≤ n, 0≤ c ≤ n-(b-a+1).

FLIP a b    // Means a FLIP operation, 1 ≤ a < b ≤ n.

The input ends up with two negative numbers, which should not be processed as a case.

Output

For each test case, you should print a line with n numbers. The ith number is the number of the ith diamond on the chain.

Sample Input

8 2
CUT 3 5 4
FLIP 2 6
-1 -1

Sample Output

1 4 3 7 6 2 5 8

题意:

n个数一开始是1~n顺序排列,有两个操作。

CUT(a, b, c)操作表示把第a到第b个数取下放到新组成的第c个数后面;

FLIP(a, b)操作表示把第a到第b个数翻转;

比如样例: n = 8

1   2   3   4   5   6   7   8

CUT(3,  5,  4)后数列变成 1   2   6   7   3   4   5   8

FLIP(2,  6)后数列变成1   4   3   7   6   2   5   8

问m次操作后的数列为?

思路:

伸展树的基础操作,区间截断,区间翻转。

区间截断:对于CUT(a, b, c)

1)提取区间[a, b]

具体方法:Splay(a - 1, T),Splay(b +1, T->right);即把第a - 1个数旋转到根,把第b + 1个数旋转到根的右儿子;那以根的右儿子的左儿子为根的子树就是所有区间[a, b]内的值;把它剪下。

2)把第c个数旋转到根,把第c + 1个数旋转到根的右儿子;那根的右儿子的左儿子肯定是空的。

3)把剪下的子树接在根的右儿子的左儿子。

区间翻转:对于FLIP(a, b)

1)提取区间[a, b]

2)在左儿子做翻转标志(注意不是简单的赋值为1,而是要做异或操作,即原来是1的标记为0,是0的标记为1)

3)在适当的地方加Push_down向下更新(跟线段树区间更新一样)

首先要明白的一点是二叉树结点的信息是最终中序遍历(一定是按下标先后顺序)输出的值,而不是下标的值;那怎样取下标为a的结点呢?

利用每个结点的sz,它表示以该结点为子树的的所有元素个数(T->sz = T->left->sz + T->right->sz + 1)

从根结点p开始

1)如果(左子树的元素个数加上1(1表示p结点) )sum = p->left->sz + 1;if (sum == a) ,那就找到了,返回p结点

2)sum比a小,说明要往右走(p = p->right);同时a -= sum;跳到步骤1

3)sum比a大,说明要往左走(p = p->right);a无需变动;跳到步骤1

怎样书写Push_down函数

就把左右子树对调,然后取消该结点标记,把两棵子树添加标记。

注意:修改时要注意Push_down的地方,注意父亲指针;注意更新sz;用动态的方法时要注意儿子是否为空。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))

typedef struct TREE
{
    int data;
    TREE *fa, *l, *r;
    int sz; //以该结点为根的树的总结点数
    bool flag; //翻转标记
}Tree;

bool space;

void Push_down(Tree *T)
{
    if(NULL == T) return;
    //左右子树对调
    if(T->flag){
        Tree *tmp;
        tmp = T->r;
        T->r = T->l;
        T->l = tmp;
        T->flag = 0;
        if(T->l) T->l->flag ^= 1;
        if(T->r) T->r->flag ^= 1;
    }
}

void Init(Tree *&T, int n)
{
    int i;
    bool k = 0;
    Tree *cur, *pre;
    for(i = n + 1; i > -1; i--){
        cur = (Tree *)malloc(sizeof(Tree));
        cur->data = i;
        cur->fa = cur->l = cur->r = NULL;
        cur->sz = 1;
        cur->flag = 0;
        if(k){
            cur->r = pre;
            pre->fa = cur;
            cur->sz = pre->sz + 1;
        }
        if(!k) k = 1;
        pre = cur;
    }
    T = cur;
}

void PreOrder(Tree *T)
{
    if(NULL == T) return;
    printf("%d ", T->data);
    PreOrder(T->l);
    PreOrder(T->r);
}

void MidOrder(Tree *T, int n)
{
    if(NULL == T) return;
    Push_down(T);
    MidOrder(T->l, n);
    if(T->data > 0 && T->data < n + 1){
        if(space) printf(" ");
        else space = 1;
        printf("%d", T->data);
    }
    MidOrder(T->r, n);
}

void R_rotate(Tree *x)
{
    Tree *y = x->fa;
    Tree *z = y->fa;
    Tree *k = x->r;
    int sx = x->sz, sy = y->sz, sk = 0;
    if(k) sk = k->sz;
    y->l = k;
    x->r = y;
    if(z){
        if(y == z->l) z->l = x;
        else z->r = x;
    }
    if(k) k->fa = y;
    y->fa = x;
    x->fa = z;
    y->sz = sy - sx + sk;
    x->sz = sx - sk + y->sz;
}

void L_rotate(Tree *x)
{
    Tree *y = x->fa;
    Tree *z = y->fa;
    Tree *k = x->l;
    int sx = x->sz, sy = y->sz, sk = 0;
    if(k) sk = k->sz;
    y->r = k;
    x->l = y;
    if(z){
        if(y == z->r) z->r = x;
        else z->l = x;
    }
    if(k) k->fa = y;
    y->fa = x;
    x->fa = z;
    y->sz = sy - sx + sk;
    x->sz = sx - sk + y->sz;
}

//寻找第x个数的结点
Tree *FindTag(Tree *T, int x)
{
    if(NULL == T) return NULL;
    Push_down(T);
    Tree *p;
    p = T;
    int sum = (p->l ? p->l->sz : 0) + 1;
    while(sum != x && p)
    {
        if(sum < x){
            p = p->r;
            x -= sum;
        }
        else p = p->l;
        Push_down(p);
        sum = (p->l ? p->l->sz : 0) + 1;
    }
    Push_down(p);
    return p;
}

void Splay(int x, Tree *&T)
{
    Push_down(T);
    Tree *p, *X, *end, *new_t;
    end = T->fa;
    new_t = T;
    if(end) new_t = T->fa;
    X = FindTag(new_t, x);
    while(X->fa != end)
    {
        p = X->fa;
        if(end == p->fa){ //p是根结点
            if(X == p->l) R_rotate(X);
            else L_rotate(X);
            break;
        }
        //p不是根结点
        if(X == p->l){
            if(p == p->fa->l){
                R_rotate(p); //LL
                R_rotate(X); //LL
            }
            else{
                R_rotate(X); //RL
                L_rotate(X);
            }
        }
        else{
            if(p == p->fa->r){ //RR
                L_rotate(p);
                L_rotate(X);
            }
            else{ //LR
                L_rotate(X);
                R_rotate(X);
            }
        }
    }
    T = X;
}

void CUT(Tree *&T, int a, int b, int c)
{
    //取[a,b]
    Splay(a - 1, T);
    Splay(b + 1, T->r);
    //剪[a,b]
    Tree *tmp;
    tmp = T->r->l;
    tmp->fa = NULL;
    T->r->l = NULL;
    T->r->sz -= tmp->sz;
    T->sz -= tmp->sz;
    //移动第c个数到根结点,第c+1个数到根结点右儿子
    //这样根结点右儿子的左儿子必然为空,就可以把剪掉的放上去
    Splay(c, T);
    Splay(c + 1, T->r);
    //接[a, b]
    T->r->l = tmp;
    tmp->fa = T->r;
    T->r->sz += tmp->sz;
    T->sz += tmp->sz;
}

void FLIP(Tree *&T, int a, int b)
{
    //取[a,b]
    Splay(a - 1, T);
    Splay(b + 1, T->r);
    //标记T->r->l
    T->r->l->flag ^= 1;
}

void FreeTree(Tree *T)
{
    if(NULL == T) return;
    FreeTree(T->l);
    FreeTree(T->r);
    free(T);
}

int main()
{
    //freopen("in.txt", "r", stdin);
    Tree *T;
    int n, q, a, b, c;
    char s[6];
    while(scanf("%d%d", &n, &q), n >= 0 && q >= 0)
    {
        space = 0;
        T = NULL;
        Init(T, n);
        while(q--)
        {
            scanf("%s", s);
            if('C' == s[0]){
                scanf("%d%d%d", &a, &b, &c);
                CUT(T, a + 1, b + 1, c + 1);
            }
            else{
                scanf("%d%d", &a, &b);
                FLIP(T, a + 1, b + 1);
            }
        }
        MidOrder(T, n);
        printf("\n");
        FreeTree(T);
    }
    return 0;
}
时间: 2024-11-08 15:34:38

Splay树——HDU 3487 Play with Chain的相关文章

hdu 3487 Play with Chain(splay区间剪切,翻转)

题目链接:hdu 3487 Play with Chain 题意: cut a b c: 将a到b区间剪切下来,放在第c位置的后面. flip a b: 翻转a到b区间 题解: 第一个操作,选通过旋转,然后使a到b区间变成根的右儿子的左儿子,然后剪掉. 再找到c+1的位置,接上. 第二个操作,区间标记就行. 1 #include<bits/stdc++.h> 2 #define F(i,a,b) for(int i=a;i<=b;++i) 3 using namespace std; 4

hdu 3487 Play with Chain (Splay)

hdu 3487 Splay树模板题 题意: 一开始给出1 2 3 4 ... n 这样一个序列,对这个序列进行以下两种操作: (1)CUT a b c: 将子串[a,b]切下来,放到剩余串的第c个数之后 . (2) FLIP a b : 将子串[a,b]翻转,如 1 2 3 4 就变成 4 3 2 1 . 总之就是一道Splay树的模板题 ... 1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm&

hdu 3487 Play with Chain

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3487 YaoYao is fond of playing his chains. He has a chain containing n diamonds on it. Diamonds are numbered from 1 to n.At first, the diamonds on the chain is a sequence: 1, 2, 3, …, n.He will perform t

HDU 3487 Play with Chain 【Splay】

1-n的序列,有两种操作: 1,将一段区间翻转 2,将一段区间切下来放到剩余序列的第C个数后 采用延迟更新的方法维护区间的翻转,并维护一个size域. 添加一个最大点和一个最小点,防止出界 翻转时,将第L-1个点伸展到跟,再将第R+1个点伸展到L-1的右子树,这时R+1的左子树就是要翻转的区间,加上一个标记. 切区间时,跟翻转操作差不多,只是不加标记.然后找到C+1和C,将C伸展到根,C+1伸展到C的右子树,此时C+1的左子树就是要插入的位置. 其实我说了这么多并没有什么卵用....最后还是得自

Splay树(区间更新)—— POJ 3468 A Simple Problem with Integers

对应POJ 题目:点击打开链接 A Simple Problem with Integers Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 72765   Accepted: 22465 Case Time Limit: 2000MS Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operati

两个队列+k叉哈夫曼树 HDU 5884

1 // 两个队列+k叉哈夫曼树 HDU 5884 2 // camp题解: 3 // 题意:nn个有序序列的归并排序.每次可以选择不超过kk个序列进行合并,合并代价为这些序列的长度和.总的合并代价不能超过TT, 问kk最小是多少. 4 // . 5 // 题解:首先二分一下这个kk.然后在给定kk的情况下,这个代价其实就是kk叉的哈夫曼树问题.因此直接套用哈夫曼树的堆做法即可.复杂度O(nlog^2n) 6 // ?,这样优化一下读入是可以卡过去的. 7 // 然后主代码手表示,利用合并的单调

HDOJ 3487 Play with Chain

前言 在编程过程中总结归纳出来的一种编程经验,从而形成的设计思想称为设计模式. 设计模式有23种.它适用于所有的编程语言. 常用的有创新型的设计模式:简单工厂.抽象工厂和单例模式:行为型的设计模式:模板设计模式.观察者模式和命令模式:结构性的设计模式:适配器设计模式.代理模式(静态和动态两种,典型的有在spring的AOP编程中使用)和装饰器设计模式. 正文 单例模式(singleton) 保证一个类在内存中只能创建一个实例. 1.实现步骤: 1)将构造器私有化,即使用private修饰构造器

poj 3468 Splay 树

大二上的时候,写过一个AVL的操作演示,今天一看Splay,发现和AVL其实一样,加上线段树的基础,懒惰标记什么都知道,学起来轻松许多哦 我参考的模板来自这里  http://blog.csdn.net/u013480600/article/list/2 里面有大量的ch[r][0] ch[r][1]等 我建议用宏定义取代,写的时候方括号少打了很多,等做的题多得时候,我再把自己使用的模板发来 #include <cstdio> #include <cstring> #include

cf 420D. Cup Trick (Splay树)

Splay  树的比较基本的序列维护操作, 用getSeg( int l, int r) 获取要操作的区间 若:获取区间[l,r],非空,getSeg(l,r),然后KT指向区间[l,r] 若:获取区间为空,如当要在l位置插入一个值时,则getSeg(l,l-2),然后可在KT赋值新节点 D. Cup Trick #include <iostream> #include <cstdio> #include <cstring> #include <string>