splay总结

splay树对于区间操作比线段树更容易操作,编写代码也更容易,调试起来也很方便。以下我大概说说splay的几个操作以及我对几个题目的分析。

首先是资料,请点击 here 下载(如果不行的话,麻烦跟我说一下,谢谢)

 

以下我们先定义几个数组和变量:

// lx表示x的左儿子,rx表示x的右儿子,px表示x的父节点,root表示根节点,其他的类似
#define lx ch[x][0]
#define rx ch[x][1]
#define px pre[x]

#define ly ch[y][0]
#define ry ch[y][1]
#define py pre[y]

#define lz ch[z][0]
#define rz ch[z][1]
#define pz pre[z]

#define rt ch[root][1]
#define lrt ch[rt][0]
// ch数组表示x的左右儿子,pre表示x的父节点,sz表示大小,rev表示区间翻转标记,str表示插入的字符串
int ch[X][2],pre[X],sz[X],rev[X]; 
char str[X];

  

首先,对于初始化建树的问题。我们首先需要建立两个或者以上的额外节点,建立的过程如下图:

  (节点1,2都是一个额外的节点(跟题目没有半毛钱关系。。。))

比如我们需要用splay维护字符串abcdefgh:

1.建树,如上

2.在 根的右儿子的左儿子 中插入

3.为了使得树的深度不太深,我们需要递归把字符串加在 根的右儿子的左儿子 上。建完树之后应该如下:(可以假设两个虚拟节点1,2的字符为未出现在题目中的字符)

该代码如下:

    // 初始化建树的问题
  inline void new_node(int &x,int y,int c){ // 注意x需要在这修改,修改之后的值需要传回上一个函数
        x = ++tot;
        ha[x] = val[x] = c;
        px = y;
        lx = rx = 0;
    }
 
    inline void build(int &x,int y,int l,int r){ // 对于区间[l,r]的插入
        if(l>r) return;
        int mid = (l+r)>>1;
        new_node(x,y,str[mid]-'a'+1);  // 其他的题目不一定是str数组了
        build(lx,x,l,mid-1);
        build(rx,x,mid+1,r);
        update(x);
    }
  
 
  inline void init(){
    // 初始化,如果只用一次的话,我们定义的是全局变量,所以不用置为0,如果需要的话,我们也可以直接把根的所有有关数组置为0就行了,无需把全部清零
        //memset(ch,0,sizeof(ch));
        //memset(sz,0,sizeof(sz));
        //memset(pre,0,sizeof(pre));
 
        root = tot = 0;
        new_node(root,0,0);
        new_node(rt,root,0);
 
        update(rt);  // 需要更新一下新插入的节点,具体看题目而定
        update(root);
 
        gets(str);
        int n = strlen(str);
        build(lrt,rt,0,n-1);
        update(rt);
        update(root);
    }

  

 update函数一般的写法如下:

    inline void update(int x){
        sz[x] = sz[lx]+sz[rx]+1;
        // 如果需要维护一下sum,lsum,rsum或者hash的话,在这里添加相应代码
    }

push_down函数看具体的题目而定,主要是及时把lazy标记下沉

然后是splay和旋转操作,由于给出的资料或者其它的一些资料已经很详细了,这里不细说。splay(x,goal)操作就是把x旋转到x的父节点为goal的操作,他是自底向上的操作。在操作过程中,zag,zig其实就相当于普通的左旋、右旋。以下为代码:

	inline int sgn(int x){ // 判断x是y的左或者右儿子,0左,1右
        return ch[px][1]==x;
    }
    inline void setc(int y,int d,int x){ // 把x置为y的儿子
        ch[y][d] = x;
        px = y;
    }

    inline void rot(int x,int d){ // 旋转操作,自己动手画画就好了
        int y = px;
        int z = py;
        //push_down(y);
        //push_down(x);
        setc(y,!d,ch[x][d]);
        if(z)   setc(z,sgn(y),x);
        px = z;
        setc(x,d,y);
        update(y);
    }

    inline void splay(int x,int goal=0){ // splay的关键操作
        if(!x)
            return;
        //push_down(x);
        while(px!=goal){
            int y = px;
            int z = py;
            if(z==goal){
                rot(x,!sgn(x));
                break;
            }
            if(lz==y){
                ly==x?rot(y,1):rot(x,0);
                rot(x,1);
            }
            else{
                ry==x?rot(y,0):rot(x,1);
                rot(x,0);
            }
        }
        update(x);
        if(goal==0)
            root = x;
    }

  

  

下面我们主要说说splay具体能够做什么

首先,我们需要知道第k小、得到以x为根的最小值元素,直接贴代码了。。

    inline int get_Kth(int x,int k){
        //push_down(x);   // 是否需要把标记下沉
        int tmp = sz[lx]+1;
        if(tmp==k)
            return x;
        if(k<tmp)
            return get_Kth(lx,k);
        return get_Kth(rx,k-tmp);
    }

    inline int get_min(int x){
        //push_down(x);
        while(lx){
            x = lx;
            //push_down(x);
        }
        return x;
    }

  

为了方便描述,rt表示根的右儿子(right_root),  lrt表示根的右儿子的左儿子(left_right_root)

1.插入操作:在位置k上插入一个元素。

  我们首先需要得到第k+1小的元素下标x(这里的小指的是树中按照sz大小来决定),然后把x旋转至根,旋转完了之后,我们需要得到x的后继y,把y旋转至根的右儿子。这时,我们发现根的右儿子的左子树为空。然后我们在lrt中新建一个节点就行了。

  为什么找的不是第k小的元素呢?还记得我们为了代码更加简单,构造的两个虚拟节点吗?刚好实在插入的所有元素的两端,并且插入的时候实际上是该元素的后面,所以我们找的是第k+1小的元素。

  比如我们在@abcdefgh@的树中,在第4位后面插入新节点q,我们插入的位置是d,e的中间,即第5位和5的后继

     inline void Insert(){ // 插入操作
        int x,k;
        char s[2];
        scanf("%d%s",&k,s);

        x = get_Kth(root,k+1); // 为什么不是k?
        splay(x);
        int y = get_Min(rt); // 得到x的后继
        splay(y,root);

        new_node(lrt,rt,s[0]-'a'+1); // 新建一个节点

        update(lrt); //及时更新插入之后的信息!
        update(rt);
        update(root);
    }

  

2.插入操作二:成段插入。

  假设我们需要插入的位置为pos,然后插入n个元素,那么插入的操作的过程是:首先寻找pos+1的元素x,然后找到他的后继,再把n个元素通过build递归建在lrt上。

    inline void Insert(){
        int pos,n;
        scanf("%d%d",&pos,&n);
        rep(i,k)
            cin>>str[i];

        pos ++;

        int x = get_Kth(root,pos);
        splay(x);
        int y = get_min(rt);
        splay(y,root);

        build(lrt,rt,0,n-1);
        //update(lrt); 可以不需要,在build函数中已经更新
        update(rt);
        update(root);
    }

  

3.删除操作:删除一个区间(一个节点其实也是小区间)

   假设我们需要删除区间[a,b],我们需要找到a的前驱以及b的后继,使得以lrt为根子树恰好为区间[a,b]。首先,我们需要找到第a小元素x,然后把x splay到根,然后再找到b的后继,即找到第b+2小的元素y把y splay到根的右儿子,这时,我们发现区间[a,b]的元素实际上就是以lrt为根的子树的所有元素

  为什么这个时候是a而不是a+1呢?因为第a小才是实际上的a的前驱,而我们执行完上述的操作之后,lrt才是真正的区间[a,b]。这个时候根为区间[a,b]的前一个元素,b+2为后一个元素。

  然后我们把lrt的信息以及rt的信息修改一下就行了,最后需要update(rt),update(root);及时更新一下rt和root

以下为代码:

    inline void Delete(){ // 删除区间 [a,b]
        int a,b;
        scanf("%d%d",&a,&b);
        int x = get_Kth(root,a);
        int y = get_Kth(root,b+2);
        splay(x);
        splay(y,root);
        //del(lrt); 建立内存池,人工回收节点编号(在删除操作太频繁的时候,可能节点的编号使得数组太大,内存不足)
        pre[lrt] = 0;
        lrt = 0;
        update(rt);
        update(root);
    }

  

 4.翻转操作:把区间[a,b]翻转

  我们用一个rev[]数组进行类似于线段树的lazy标记。

  同样我们先把 第a小 旋转到到根,第b+2小 旋转到rt,区间[a,b]即为lrt。这时,我们对于节点lrt的rev标记置反就好了。注意这个时候向不向上更新根据具体的题目而定,一般是不需要的。代码:

    inline void Rotate(){
        int a,b;
        scanf("%d%d",&a,&b);
        int x = get_Kth(root,a);
        splay(x);
        int y = get_Kth(root,b+2);
        splay(y,root);
        rev[lrt] ^= 1;
    }

注意:由于我们有了翻转标记,所以在标记下沉的时候,如果有标记的话,需要把左右子树交换一下。同时,如果有其他的数据维护,考虑一下需不需要交换。。。具体的看 S级别的NOI2005维修数列 的splay操作,做完之后,基本上的区间操作都会做了。

 5.区间赋值:把区间[a,b]的所有元素置为c

  其实我们发现这个跟上面翻转操作基本一样。所以我直接上代码了。。。

    inline void Make_same(){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);

        int x = get_Kth(root,a);
        int y = get_Kth(root,b+2);
        splay(x);
        splay(y,root);

        update_same(lrt,c);
        update(rt);
        update(root);
    }

注意:有标记,考虑下沉时候的操作(需要维护sum,lmax,rmax,val等等...)。  

6.区间求和:询问区间[a,b]的和

  同样把区间[a,b]定在lrt上面,然后直接调用sum[lrt]就行了。那么如何维护sum[x]呢?其实很简单,在向上update的时候就及时更新好了。

    inline void update(int x){
        sz[x] = sz[lx]+sz[rx]+1;
        sum[x] = sum[lx]+sum[rx]+val[x]; // 多了这个
    }

    inline void Get_sum(){
        int a,b;
        scanf("%d%d",&a,&b);
        int x = get_Kth(root,a);
        int y = get_Kth(root,b+2);
        splay(x);
        splay(y,root);
        printf("%d\n",sum[lrt]);
    }

  

 7.区间求最值:询问区间[a,b]的最值

  那这个是不是跟sum的操作基本一样呢?代码略了。

8.求和的最大子序列:询问区间[a,b]的最大子段和

  想法是:对于每个节点,用数组mmax[]来表示以该节点为根的区间的最大子段和。所以我们可以把区间[a,b]定格在lrt,然后直接输出mmax[lrt]。如何维护???

  我们可以在update的时候更新!我们维护一个最大前驱和lmax,一个最大后继和rmax,然后再维护一个mmax,然后我们像以下代码这样维护就好了。

    inline void update(int x){
        sz[x] = sz[lx]+sz[rx]+1;
        //sum[x] = sum[lx]+sum[rx]+val[x];
        lmax[x] = max( lmax[lx] , sum[lx]+val[x]+max(0,lmax[rx]) );
        rmax[x] = max( rmax[rx] , sum[rx]+val[x]+max(0,rmax[lx]) );
        mmax[x] = max( mmax[lx] , mmax[rx] );
        mmax[x] = max( mmax[x] , max(0,lmax[rx])+val[x]+max(0,rmax[lx]) );
    }
    
    inline void Get_max(){
        int a,b;
        int x = get_Kth(root,a);
        int y = get_Kth(root,b+2);
        splay(x);
        splay(y,root);
        printf("%d\n",mmax[lrt]);
    }

  可能你会想为什么是这么更新的?这样对的吗?想想,我们的splay是中序遍历的,然后画个图出来看看吧。

9.单点修改操作

   这个很简单吧,比如我们需要把第k个元素做修改,首先我们找到第k+1,然后旋转至根,直接把根的值修改,update(root)就好了。代码略。。。

 至于完整的模板,其实以那题NOI2005维修数列作为模板就好了。

貌似其他的操作暂时我也没做到。就这些吧。。。以下是实战区。。。(点击有链接)

 

BZOJ 1588: [HNOI2002]营业额统计


这题是我自己的第一道splay题,只是用来测试一下模板的。。。
分析:
这题就是求前驱和后继。旋转跟SBT、AVL、BST、treap一样,并且不需要维护树的平衡,默认为90%的询问发生在10%的数据上面,所以我们在插入的时候就把插入的的数据经过splay操作旋转该节点至根部。这题在询问x的前驱的时候,我们可以先得到x,然后再把x经过splay操作旋转至根部,再找到根的前驱,后继同理。其他的操作以后再总结一下。。

/*

这题是我自己的第一道splay题,只是用来测试一下模板的。。。

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1588

分析:
    这题就是求前驱和后继。旋转跟SBT、AVL、BST、treap一样,并且不需要维护树的平衡,
    默认为90%的询问发生在10%的数据上面,所以我们在插入的时候就把插入的的数据经过
    splay操作旋转该节点至根部。这题在询问x的前驱的时候,我们可以先得到x,然后再把x
    经过splay操作旋转至根部,再找到根的前驱,后继同理。其他的操作以后再总结一下。。

*/
#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

#define debug puts("here")
#define rep(i,n) for(int i=0;i<n;i++)
#define foreach(i,vec) for(unsigned i=0;i<vec.size();i++)
#define pb push_back

#define INF 1e9

namespace Splay{

#define X 1111111

#define px pre[x]
#define py pre[y]

#define lx ch[x][0]
#define ly ch[y][0]
#define lz ch[z][0]

#define rx ch[x][1]
#define ry ch[y][1]

    int root,tot;
    int ch[X][2],pre[X],val[X];

    inline void init(){ // 初始化
        root = tot = 0;
        memset(ch,0,sizeof(ch));
        memset(pre,0,sizeof(pre));
        memset(val,0,sizeof(val));
    }

    inline void dfs(int x){ // debug使用
        if(x){
            dfs(lx);
            printf("self = %d , left = %d , right = %d , father = %d\n",x,lx,rx,px);
            dfs(rx);
        }
    }

    inline void new_node(int &x,int father,int v){ // 构造新节点
        x = ++tot;
        pre[x] = father;
        val[x] = v;
        ch[x][0] = ch[x][1] = 0;
    }

    inline void setc(int y,int d,int x){ // 旋转过程的子树的链接
        ch[y][d] = x;
        pre[x] = y;
    }

    inline int sgn(int x){ // 0表示在左,1表示在右
        return ch[px][1]==x;
    }

    inline void _rot(int x,int d){
        int y = px;
        int z = py;

        setc(y,!d,ch[x][d]);//类似SBT,要把其中一个分支先给父节点

        if(z)   //如果父节点不是根结点,则要和父节点的父节点连接起来
            setc(z,sgn(y),x);
        pre[x] = z;

        setc(x,d,y);
    }

    inline void rot(int x){_rot(x,!sgn(x));}
    inline void zag(int x){_rot(x,0);} // 左旋
    inline void zig(int x){_rot(x,1);} // 右旋

    // Splay调整,将根为r的子树调整为goal
    inline int splay(int x,int goal=0){
        while(px!=goal){
            int y = px;
            int z = py;
            if(z==goal){
                rot(x);
                break;
            }
            if(lz==y){
                if(ly==x)
                    zig(y),zig(x);
                else
                    zag(x),zig(x);
            }
            else{
                if(ry==x)
                    zag(y),zag(x);
                else
                    zig(x),zag(x);
            }
        }
        if(goal==0)
            root = x;
        return x;
    }

    inline int insert(int v){ // 插入
        int x = root;
        while(ch[x][ val[x]<v ]){
            if(val[x]==v){
                splay(x); // 已存在,这题可以忽略掉,但是需要旋转该节点作为根
                return 0;
            }
            x = ch[x][ val[x]<v ];
        }
        new_node(ch[x][ val[x]<v ],x,v);
        splay(ch[x][ val[x]<v ]); // 新插入节点splay至根部
        return 1;
    }

    inline int get_pre(int x){ // 得到前驱
        int tmp = x;
        x = lx;
        if(x==0)
            return INF;
        while(rx)
            x = rx;
        return val[tmp]-val[x];
    }

    inline int get_next(int x){ // 得到后继
        int tmp = x;
        x = rx;
        if(x==0)
            return INF;
        while(lx)
            x = lx;
        return val[x]-val[tmp];
    }

#undef X

#undef px
#undef py

#undef lx
#undef ly
#undef lz

#undef rx
#undef ry

}using namespace Splay;

int main(){

#ifndef ONLINE_JUDGE
	freopen("sum.in","r",stdin);
#endif

    int ans = 0,n,x;
    cin >> n;

    init();
    rep(i,n){
        if(scanf("%d",&x)==EOF)
            x = 0;
        if(i==0){
            ans += x;
            new_node(root,0,x);
            continue;
        }
        if(insert(x)==0)
            continue;
        // 前面插入的时候x已经splay至根部,所以可以直接求值
        ans += min(get_pre(root),get_next(root));
    }
    cout<<ans<<endl;

	return 0;
}

  

 SGU 187. Twist and whirl - want to cheat

 对于数列,求m次区间翻转之后的数列(比较简单。。。)

/*

题目:对于数列,求m次区间翻转之后的数列

分析:splay简单操作

*/
#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

#define debug puts("here")
#define rep(i,n) for(int i=0;i<n;i++)
#define foreach(i,vec) for(unsigned i=0;i<vec.size();i++)
#define pb push_back
#define RD(n) scanf("%d",&n)

namespace Splay{

#define lx ch[x][0]
#define rx ch[x][1]
#define px pre[x]

#define ly ch[y][0]
#define ry ch[y][1]
#define py pre[y]

#define lz ch[z][0]

#define rt ch[root][1]
#define lrt ch[rt][0]

    const int MAXN = 130015;

    int ch[MAXN][2],pre[MAXN],val[MAXN],sz[MAXN];
    bool rev[MAXN];
    int root,tot;
    int ary[MAXN];
    bool ok;

    inline void update(int x){ // ok
        sz[x] = sz[lx]+sz[rx]+1;
    }

    inline void new_node(int &x,int y,int v){ // ok
        x = ++tot;
        px = y;
        rev[x] = 0;
        lx = rx = 0;
        val[x] = v;
    }

    inline void build(int &x,int y,int l,int r){ // ok
        if(l>r) return;
        int mid = (l+r)>>1;
        new_node(x,y,ary[mid]);
        build(lx,x,l,mid-1);
        build(rx,x,mid+1,r);
        update(x);
    }

    inline void push_down(int x){   // ok
        if(rev[x]==0)   return;
        swap(lx,rx);
        rev[x] = 0;
        rev[lx] ^= 1;
        rev[rx] ^= 1;
    }

    inline int sgn(int x){ // ok
        return ch[px][1]==x;
    }

    inline void setc(int y,int d,int x){ // ok
        ch[y][d] = x;
        px = y;
    }

    inline void rot(int x,int d){ // ok
        int y = px;
        int z = py;
        push_down(y);
        push_down(x);
        setc(y,!d,ch[x][d]);
        if(z)   setc(z,sgn(y),x);
        pre[x] = z;
        setc(x,d,y);
        update(y);
    }

    inline void splay(int x,int goal=0){ // ok
        push_down(x);
        while(px!=goal){
            int y = px;
            int z = py;
            if(z==goal){
                rot(x,!sgn(x));
                break;
            }
            if(lz==y){
                if(ly==x)
                    rot(y,1),rot(x,1);
                else
                    rot(x,0),rot(x,1);
            }
            else{
                if(ry==x)
                    rot(y,0),rot(x,0);
                else
                    rot(x,1),rot(x,0);
            }
        }
        update(x);
        if(goal==0)
            root = x;
    }

    inline int get_Kth(int x,int k){ // ok
        push_down(x);
        int tmp = sz[lx]+1;
        if(tmp==k)  return x;
        if(k<tmp)   return get_Kth(lx,k);
        else        return get_Kth(rx,k-tmp);
    }

    inline void dfs(int x){ // 调试用的,忽略。。
        if(x==0)    return;
        push_down(x);
        dfs(lx);
        if(val[x]){
            ok?printf(" "):ok = 1;
            printf("%d",val[x]);
        }
        dfs(rx);
    }

    inline void solve(){
        ok = 0;
        int n,m;
        scanf("%d%d",&n,&m);
        int x,y;

        root = tot = 0;
        ch[0][0] = ch[0][1] = pre[0] = rev[0] = 0;

        ary[0] = 0;
        for(int i=1;i<=n;i++)
            ary[i] = i;
        new_node(root,0,0);
        new_node(rt,root,0);

        build(lrt,rt,1,n);

        while(m--){
            scanf("%d%d",&x,&y);
            x = get_Kth(root,x);
            splay(x);
            y = get_Kth(root,y+2);
            splay(y,root);
            rev[lrt] ^= 1;
        }
        dfs(root);
        puts("");
    }

}using namespace Splay;

int main(){

#ifndef ONLINE_JUDGE
	freopen("sum.in","r",stdin);
#endif

    solve();

	return 0;
}

  

 BZOJ 1014 [JSOI2008]火星人prefix

分析:
动态计算LCP。
 
静态的话,可以用后缀数组来计算,但是对于动态的话就无能为力了。。。如果用询问来计算LCP的话,会TLE的,所以我们可以想到用二分+hash来计算。但是计算hash的话,由于是动态并且是区间统计问题,所以我们可以用splay节点表示字符,并且记录以该节点为根的子树所在的区间的字符串的hash值,判断是否相等就行了。。。
 
hash函数的选取,用RKhash:
hash[a,b] = ch[a]*1+ch[a+1]*27+...+ch[b]*27^(b-a)
 
需要注意的是:
1.对于首字符都不同的话,直接输出0
2.短字符串可能是长字符串的前缀

 其实这题都是基本的操作,不过需要注意细节(我自己调了5小时,因为有一处没有及时update...)

/*

分析:
    动态计算LCP。

    静态的话,可以用后缀数组来计算,但是对于动态的话就无能为力了。。。
    如果用询问来计算LCP的话,会TLE的,所以我们可以想到用二分+hash来计
    算。但是计算hash的话,由于是动态并且是区间统计问题,所以我们可以用
    splay节点表示字符,并且记录以该节点为根的子树所在的区间的字符串的
    hash值,判断是否相等就行了。。。

    hash函数的选取,用RKhash:
    hash[a,b] = ch[a]*1+ch[a+1]*27+...+ch[b]*27^(b-a)

    需要注意的是:
    1.对于首字符都不同的话,直接输出0
    2.短字符串可能是长字符串的前缀

*/
#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

#define debug puts("here")
#define rep(i,n) for(int i=0;i<n;i++)
#define REP(i,a,b) for(int i=a;i<b;i++)
#define foreach(i,vec) for(unsigned i=0;i<vec.size();i++)
#define pb push_back
#define RD(n) scanf("%d",&n)

namespace Splay{

#define lx ch[x][0]
#define rx ch[x][1]
#define px pre[x]

#define ly ch[y][0]
#define ry ch[y][1]
#define py pre[y]

#define lz ch[z][0]

#define rt ch[root][1]
#define lrt ch[rt][0]

    const int MAXN = 1e5+10;
    char str[MAXN];
    const int MOD = 9875321;

    int ch[MAXN][2],pre[MAXN],sz[MAXN];
    int ha[MAXN],val[MAXN];
    int p[MAXN];
    int root,tot;

    inline void update(int x){ // ok
        sz[x] = sz[lx]+sz[rx]+1;
        if(sz[0])   puts("error !!! sz[0] != 0");
        ha[x] = ll(ha[lx]+(ll)val[x]*p[ sz[lx] ]+(ll)ha[rx]*p[ sz[lx]+1 ])%MOD;
    }

    inline int sgn(int x){ // ok
        return ch[px][1]==x;
    }
    inline void setc(int y,int d,int x){ // ok
        ch[y][d] = x;
        px = y;
    }

    inline void rot(int x,int d){ // ok
        int y = px;
        int z = py;
        setc(y,!d,ch[x][d]);
        if(z)   setc(z,sgn(y),x);
        px = z;
        setc(x,d,y);
        update(y);
    }

    inline void splay(int x,int goal=0){ // ok
        if(!x)
            return;
        while(px!=goal){
            int y = px;
            int z = py;
            if(z==goal){
                rot(x,!sgn(x));
                break;
            }
            if(lz==y){
                ly==x?rot(y,1):rot(x,0);
                rot(x,1);
            }
            else{
                ry==x?rot(y,0):rot(x,1);
                rot(x,0);
            }
        }
        update(x);
        if(goal==0)
            root = x;
    }

    inline int get_Kth(int x,int k){ // ok

        int tmp = sz[lx]+1;
        if(tmp==k)  return x;
        return k<tmp?get_Kth(lx,k):get_Kth(rx,k-tmp);
    }

    inline int get_Min(int x){ // ok
        while(lx)   x = lx;
        return x;
    }

    inline void new_node(int &x,int y,int c){ // ok
        x = ++tot;
        ha[x] = val[x] = c;
        px = y;
        lx = rx = 0;
    }

    inline void build(int &x,int y,int l,int r){ // ok
        if(l>r) return;
        int mid = (l+r)>>1;
        new_node(x,y,str[mid]-'a'+1);
        build(lx,x,l,mid-1);
        build(rx,x,mid+1,r);
        update(x);
    }

    void dfs(int x){ // ok
        if(x){
            cout<<x<<" "<<lx<<" "<<rx<<" "<<sz[x]<<" "<<char(val[x]+'a'-1)<<endl;
            dfs(lx);
            dfs(rx);
        }
    }

    inline void init(){ // ok
        //memset(ch,0,sizeof(ch));
        //memset(sz,0,sizeof(sz));
        //memset(pre,0,sizeof(pre));
        //memset(ha,0,sizeof(ha));

        p[0] = 1;
        for(int i=1;i<MAXN;i++)
            p[i] = (ll)p[i-1]*27%MOD;

        root = tot = 0;
        new_node(root,0,0);
        new_node(rt,root,0);

        update(rt);
        update(root);

        gets(str);
        int n = strlen(str);
        build(lrt,rt,0,n-1);
        update(rt);
        update(root);
    }

    int has(int x,int y){ // ok
        splay( get_Kth(root,x) );
        splay( get_Kth(root,y+2),root );
        return ha[lrt];
    }

    inline void Q(){
        //puts("-------------------------------");
        int x,y;
        scanf("%d%d",&x,&y);
        if(x==y){
            printf("%d\n",tot-2-y+1);
            return;
        }

        if(x>y) swap(x,y);

        splay( get_Kth(root,x+1) );
        int tmp = val[root];
        splay( get_Kth(root,y+1) );

        //dfs(root);
        //cout<<"k = "<<get_Kth(root,y+1)<<endl;

        //cout<<"tot  = "<<tot<<endl;
        //cout<<dx<<" "<<dy<<endl;
        if(val[root] != tmp ){
            puts("0");
            return;
        }
        //cout<<"-------  "<<tot-2-y+1<<endl;
        if(tot-2-y+1==1){
            puts("1");
            return;
        }
        //debug;

        int len = tot-2-y;
        if( has(x,x+len)==has(y,y+len) ){
            printf("%d\n",tot-2-y+1);
            return;
        }

        //debug;
        int l = 1,r = tot-2-y;
        while(l<=r){
            int mid = (l+r)>>1;
            if(has(x,x+mid)==has(y,y+mid))
                l = mid+1;
            else
                r = mid-1;
        }
        printf("%d\n",l);
    }

    inline void R(){ // ok
        char s[2];
        int x;
        scanf("%d%s",&x,s);
        splay( get_Kth(root,x+1) );
        val[root] = s[0]-'a'+1;
        update(root);
    }

    inline void I(){ // ok
        int x;
        char s[2];
        scanf("%d%s",&x,s);

        x = get_Kth(root,x+1);
        //cout<<"dsadsa "<<x<<endl;
        splay(x);
        int y = get_Min(rt);
        splay(y,root);
        //cout<<y<<endl;

        new_node(lrt,rt,s[0]-'a'+1);
        update(lrt);
        update(rt);
        update(root);
        //dfs(root);
    }

    inline void solve(){ // ok
        init();
        //dfs(root);
        char op[2];
        int m;
        cin>>m;
        while(m--){
            scanf("%s",op);
            if(op[0]=='Q')      Q();
            else if(op[0]=='R') R();
            else                I();
        }
    }

};

int main(){

#ifndef ONLINE_JUDGE
    freopen("sum.in","r",stdin);
    //freopen("sum.out","w",stdout);
#endif

    Splay::solve();

    return 0;
}

  

BZOJ 1269 [AHOI2006]文本编辑器editor

题目:支持 插入,翻转,删除的功能。
分析:splay的基本操作。
1.插入,我们需要把插入的位置伸展到根,然后再把伸展之后的后一个位置伸展到根的右儿子,插入就直接把所有的数插入到根的右儿子的左子树中。
2.删除,我们把需要删除的整个区间[a,b]直接伸展到根的右儿子的左子树,即把a-1伸展到根,把b+1伸展到根的右儿子,然后把根的右儿子的左子树整个删除掉即可
3.旋转,lazy标记,每当我们往下遍历的时候,需要把lazy标记下沉。而旋转过程中,比如我们需要对区间[a,b]旋转,我们可以把a-1旋转到根,把b+1旋转到根的右儿子,然后把根的右儿子的左子树的标记翻转一下就行了。
4.输出,我们需要输出光标后的第一个字符,我们可以直接把光标的位置伸展到根,然后再求根的右儿子的最小值输出
5.移动到第k个字符后面,我们直接把光标改变,无需把该位置伸展到根,只需要用的时候再进行伸展操作
6.前移(后移),直接把光标减一(加一),无需进行伸展操作
/*

题目:支持 插入,翻转,删除的功能。
分析:splay的基本操作。
    1.插入,我们需要把插入的位置伸展到根,然后再把伸展之后的后一个位置伸展到根的
    右儿子,插入就直接把所有的数插入到根的右儿子的左子树中。
    2.删除,我们把需要删除的整个区间[a,b]直接伸展到根的右儿子的左子树,即把a-1
    伸展到根,把b+1伸展到根的右儿子,然后把根的右儿子的左子树整个删除掉即可
    3.旋转,lazy标记,每当我们往下遍历的时候,需要把lazy标记下沉。而旋转过程中,比如
    我们需要对区间[a,b]旋转,我们可以把a-1旋转到根,把b+1旋转到根的右儿子,然后把根的
    右儿子的左子树的标记翻转一下就行了。
    4.输出,我们需要输出光标后的第一个字符,我们可以直接把光标的位置伸展到根,然后再
    求根的右儿子的最小值输出
    5.移动到第k个字符后面,我们直接把光标改变,无需把该位置伸展到根,只需要用的时候
    再进行伸展操作
    6.前移(后移),直接把光标减一(加一),无需进行伸展操作

*/
#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

#define debug puts("here")
#define rep(i,n) for(int i=0;i<n;i++)
#define foreach(i,vec) for(unsigned i=0;i<vec.size();i++)
#define pb push_back

int tot,root,pos;

namespace Splay{

#define lx ch[x][0]
#define rx ch[x][1]
#define px pre[x]

#define ly ch[y][0]
#define ry ch[y][1]
#define py pre[y]

#define lz ch[z][0]
#define rz ch[z][1]
#define pz pre[z]

#define rt ch[root][1]
#define lrt ch[rt][0]

    const int X = (1<<21)+5;

    int ch[X][2],pre[X],sz[X],rev[X];
    char str[X];
    char qq[X];

    inline void dfs(int x){
        if(x){
            dfs(lx);
            cout<<lx<<" "<<rx<<" "<<x<<endl;
            dfs(rx);
        }
    }

    inline void update(int x){
        //cout<<lx<<" "<<rx<<" "<<sz[lx]<<" "<<sz[rx]<<" "<<sz[lx]+sz[rx]+1<<endl;
        sz[x] = sz[lx]+sz[rx]+1;
    }

    inline void push_down(int x){
        if(x&&rev[x]){
            swap(lx,rx);
            rev[lx] ^= 1;
            rev[rx] ^= 1;
            rev[x] = 0;
        }
    }

    inline void new_node(int &x,int y,char c){
        x = ++tot;
        pre[x] = y;
        rev[x] = ch[x][0] = ch[x][1] = 0;
        str[x] = c;
    }

    inline void build(int &x,int l,int r,int y,char *s){
        if(l>r) return;
        int mid = (l+r)>>1;
        new_node(x,y,s[mid]);
        build(lx,l,mid-1,x,s);
        build(rx,mid+1,r,x,s);
        update(x);
    }

    inline void setc(int y,int d,int x){
        ch[y][d] = x;
        pre[x] = y;
    }

    inline int sgn(int x){
        return ch[px][1]==x;
    }

    inline void _rot(int x,int d){
        int y = px;
        int z = py;
        push_down(y);
        push_down(x);
        setc(y,!d,ch[x][d]);
        if(z)   setc(z,sgn(y),x);
        pre[x] = z;
        setc(x,d,y);
        update(y);
    }

    inline void rot(int x){_rot(x,!sgn(x));}
    inline void zag(int x){_rot(x,0);}
    inline void zig(int x){_rot(x,1);}


    inline int splay(int x,int goal=0){
        push_down(x);
        while(px!=goal){
            int y = px;
            int z = py;
            if(z==goal){
                rot(x);
                break;
            }
            if(lz==y){
                if(ly==x)
                    zig(y),zig(x);
                else
                    zag(x),zig(x);
            }
            else{
                if(ry==x)
                    zag(y),zag(x);
                else
                    zig(x),zag(x);
            }
        }
        update(x);
        if(goal==0)
            root = x;
        return x;
    }

    inline int get_Kth(int x,int k){
        push_down(x);
        int tmp = sz[lx]+1;
        if(tmp==k)
            return x;
        if(k<tmp)
            return get_Kth(lx,k);
        else
            return get_Kth(rx,k-tmp);
    }

    inline int get_min(int x){
        push_down(x);
        while(lx){
            x = lx;
            push_down(x);
        }
        return x;
    }

    inline void Move(){
        int k;
        scanf("%d",&k);
        pos = k+1;
    }

    inline void Insert(){
        int k;
        scanf("%d",&k);
        getchar();
        gets(qq);
        int x = get_Kth(root,pos);
        splay(x);
        x = get_min(rt);
        splay(x,root);
        build(lrt,0,k-1,rt,qq);
    }

    inline void Delete(){
        int k;
        scanf("%d",&k);
        int x = get_Kth(root,pos);
        splay(x);
        int y = get_Kth(root,pos+k+1);
        splay(y,root);

        pre[lrt] = 0;
        lrt = 0;
        update(rt);
        update(root);
    }

    inline void Rotate(){
        int k;
        scanf("%d",&k);
        int x = get_Kth(root,pos);
        splay(x);
        int y = get_Kth(root,pos+k+1);
        splay(y,root);
        rev[lrt] ^= 1;
    }

    inline void Get(){
        int x = get_Kth(root,pos);
        splay(x);
        x = get_min(rt);
        printf("%c\n",str[x]);
    }

    inline void Prev(){
        pos --;
    }

    inline void Next(){
        pos ++;
    }

    inline void init(){
        memset(pre,0,sizeof(pre));
        memset(ch,0,sizeof(ch));
        memset(sz,0,sizeof(sz));
        memset(rev,0,sizeof(rev));
        root = tot = 0;
        char s[] = "@@@@@@";
        build(root,0,5,0,s);
        pos = 1;
    }

}using namespace Splay;

int main(){

#ifndef ONLINE_JUDGE
	freopen("sum.in","r",stdin);
	//freopen("sum.out","w",stdout);
#endif

    int ncase;
    char op[20];
    cin>>ncase;
    init();
    while(ncase--){
        scanf("%s",op);
        switch(op[0]){
            case 'M':Move();break;
            case 'I':Insert();break;
            case 'D':Delete();break;
            case 'R':Rotate();break;
            case 'G':Get();break;
            case 'P':Prev();break;
            default:Next();
        }
    }
	return 0;
}

  

BZOJ 1500 [NOI2005]维修数列

题目:
插入、删除、区间赋值、翻转、求总和、求最大子序列
 
分析:
前面的都是跟文本编辑器一样的操作,而在求最大子序列的时候,我们需要维护lmax,rmax,mmax三个值,每次下沉的时候都需要更新
需要建立内存池,把内存回收然后再利用。不然的话,内存不足以令数组开到那么大。

 

/*

题目:
    插入、删除、区间赋值、翻转、求总和、求最大子序列

分析:
    前面的都是比较简单的splay操作,而在求最大子序列的时候,我们需要维护
    lmax,rmax,mmax三个值,每次下沉的时候都需要更新

*/
#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

#define debug puts("here")
#define rep(i,n) for(int i=0;i<n;i++)
#define foreach(i,vec) for(unsigned i=0;i<vec.size();i++)
#define pb push_back
#define RD(n) scanf("%d",&n)


namespace Splay{

#define lx ch[x][0]
#define rx ch[x][1]
#define px pre[x]

#define ly ch[y][0]
#define ry ch[y][1]
#define py pre[y]

#define lz ch[z][0]

#define rt ch[root][1]
#define lrt ch[rt][0]

    const int MAXN = 500005;
    const int INF = 1e9;

    int n,m;
    int root,tot;
    int sta[MAXN],top;
    int pre[MAXN],ch[MAXN][2],sz[MAXN],val[MAXN];
    int sum[MAXN];
    int lmax[MAXN],rmax[MAXN],mmax[MAXN];
    bool rev[MAXN],same[MAXN];
    int aa[MAXN];

    inline void update_same(int x,int v){
        if(x==0)    return;
        val[x] = v;
        same[x] = true;
        sum[x] = sz[x]*v;
        mmax[x] = lmax[x] = rmax[x] = max(sum[x],v);
    }

    inline void update_rev(int x){
        if(x==0)    return;
        rev[x] ^= 1;
        swap(lx,rx);
        swap(lmax[x],rmax[x]);
    }

    inline void update(int x){
        sz[x] = sz[lx]+sz[rx]+1;
        sum[x] = sum[lx]+sum[rx]+val[x];
        lmax[x] = max( lmax[lx] , sum[lx]+val[x]+max(0,lmax[rx]) );
        rmax[x] = max( rmax[rx] , sum[rx]+val[x]+max(0,rmax[lx]) );
        mmax[x] = max( mmax[lx] , mmax[rx] );
        mmax[x] = max( mmax[x] , max(0,lmax[rx])+val[x]+max(0,rmax[lx]) );
    }

    inline void push_down(int x){
        if(rev[x]){
            update_rev(lx);
            update_rev(rx);
            rev[x] = 0;
        }
        if(same[x]){
            update_same(lx,val[x]);
            update_same(rx,val[x]);
            same[x] = 0;
        }
    }

    inline int sgn(int x){
        return ch[px][1]==x;
    }

    inline void setc(int y,int d,int x){
        ch[y][d] = x;
        px = y;
    }

    inline void rot(int x,int d){
        int y = px;
        int z = py;
        push_down(y);
        push_down(x);
        setc(y,!d,ch[x][d]);
        if(z)   setc(z,sgn(y),x);
        pre[x] = z;
        setc(x,d,y);
        update(y);
    }

    inline void splay(int x,int goal=0){
        push_down(x);
        while(px!=goal){
            int y = px;
            int z = py;
            if(z==goal){
                rot(x,!sgn(x));
                break;
            }
            if(lz==y){
                if(ly==x)
                    rot(y,1),rot(x,1);
                else
                    rot(x,0),rot(x,1);
            }
            else{
                if(ry==x)
                    rot(y,0),rot(x,0);
                else
                    rot(x,1),rot(x,0);
            }
        }
        update(x);
        if(goal==0)
            root = x;
    }

    inline int get_Kth(int x,int k){
        push_down(x);
        int tmp = sz[lx]+1;
        if(tmp==k)
            return x;
        if(k<tmp)
            return get_Kth(lx,k);
        return get_Kth(rx,k-tmp);
    }

    inline int get_min(int x){
        push_down(x);
        while(lx){
            x = lx;
            push_down(x);
        }
        return x;
    }

    inline void del(int x){
        if(x==0)    return;
        sta[top++] = x;
        del(lx);
        del(rx);
    }

    inline void Delete(){
        int pos,k;
        scanf("%d%d",&pos,&k);
        int x = get_Kth(root,pos);
        int y = get_Kth(root,pos+k+1);
        splay(x);
        splay(y,root);
        del(lrt);
        pre[lrt] = 0;
        lrt = 0;
        update(rt);
        update(root);
    }

    inline void Make_same(){
        int pos,k,v;
        scanf("%d%d%d",&pos,&k,&v);

        int x = get_Kth(root,pos);
        int y = get_Kth(root,pos+k+1);
        splay(x);
        splay(y,root);

        update_same(lrt,v);
        update(rt);
        update(root);
    }

    inline void Make_rev(){
        int pos,k;
        scanf("%d%d",&pos,&k);

        int x = get_Kth(root,pos);
        int y = get_Kth(root,pos+k+1);

        splay(x);
        splay(y,root);

        update_rev(lrt);
    }

    inline void new_node(int &x,int y,int v){
        if(top) x = sta[--top]; // 从内存池中取出编号
        else    x = ++tot;     //内存池为空
        ch[x][0] = ch[x][1] = 0;
        val[x] = sum[x] = lmax[x] = rmax[x] = mmax[x] = v;
        rev[x] = same[x] = 0;
        pre[x] = y;
    }

    inline void build(int &x,int y,int l,int r){
        if(l>r) return;
        int mid = (l+r)>>1;
        new_node(x,y,aa[mid]);
        build(lx,x,l,mid-1);
        build(rx,x,mid+1,r);
        update(x);
    }

    inline void Insert(){
        int pos,k;
        scanf("%d%d",&pos,&k);
        rep(i,k)
            RD(aa[i]);

        pos ++;

        int x = get_Kth(root,pos);
        splay(x);
        int y = get_min(rt);
        splay(y,root);

        build(lrt,rt,0,k-1);
        update(rt);
        update(root);
    }

    inline void Get_sum(){
        int pos,k;
        scanf("%d%d",&pos,&k);
        int x = get_Kth(root,pos);
        int y = get_Kth(root,pos+k+1);
        splay(x);
        splay(y,root);
        printf("%d\n",sum[lrt]);
    }

    inline void Get_max(){
        int pos = 1,k = sz[root]-2;
        int x = get_Kth(root,pos);
        int y = get_Kth(root,pos+k+1);
        splay(x);
        splay(y,root);
        printf("%d\n",mmax[lrt]);
    }

    inline void init(){
        root = tot = top = 0;
        ch[0][0] = ch[0][1] = 0;
        pre[0] = sz[0] = 0;
        sum[0] = same[0] = rev[0] = 0;
        lmax[0] = rmax[0] = mmax[0] = -INF;

        new_node(root,0,-1);
        new_node(rt,root,-1);

        update(rt);
        update(root);

        RD(n);
        RD(m);
        rep(i,n)
            RD(aa[i]);

        build(lrt,rt,0,n-1);
        update(rt);
        update(root);
    }

}using namespace Splay;


int main(){

#ifndef ONLINE_JUDGE
	freopen("sum.in","r",stdin);
#endif

    init();
    char op[20];
    while(m--){
        scanf("%s",op);
        //puts(op);
        if(op[0]=='I')
            Insert();
        else if(op[0]=='D')
            Delete();
        else if(op[0]=='R')
            Make_rev();
        else if(op[0]=='G')
            Get_sum();
        else if(op[2]=='X')
            Get_max();
        else
            Make_same();
    }

	return 0;
}

  

原文地址:https://www.cnblogs.com/yejinru/p/2934385.html