bzoj1500 修改数列 区间splay树讲解

来源:互联网 发布:淘宝上买的steamaj辅助 编辑:程序博客网 时间:2024/06/07 03:52

      仅以此篇blog记录下区间splay树需要说明的几个方面,以供日后二次学习时使用

1、splay树的旋转

      要知道普通splay树一字型和之字型旋转是怎么实现的

2、区间splay树的含义:

      区间splay树,就是用splay树来维护一个数列及其相关信息。

      首先要搞清楚这棵树是怎么组织的:splay树作为平衡树的一种,满足左小右大的性质,那么这里的“大”和“小”指的是什么的大和小呢?注意,这里指的是数列下标的大小,也就是说,区间splay是按照下标的大小把线性的数列变成了树型结构。这样一来,以一个点x为根中序遍历这个子树,得到的就是原数列的一个连续的子序列a[l , l + 1 , ... , r],如果以整棵树的根遍历的话得到的就原数列a[1 , 2 , ... , n]。

     知道了这棵树怎么组织的,下一步就要知道树上每个节点的含义:树上的每个节点要维护的信息有两类,第一类是这个节点对应的数列中的元素的信息,比如5号节点对应的是数列中的第7个数,即a[7];第二类是这个节点为根的子树对应的区间的信息,这个信息可以是这个区间的最大值,总和,甚至是这个区间构成的字符串的hash值(bzoj1014),举个例子,以5号节点为根的子树对应的区间是a[3, 4 , 5 , 6 , 7],那么maxv[5]就表示a[3] ~ a[7]中的最大值。

3、区间splay树的增删改查插入翻转

   实现将数列一个区间进行增删改查插入翻转的功能,在splay上实现的思想都是相似的,首先就是要找到这个要进行操作的区间:

  

   比如我要对a[a , a + 1 , ... , b]这个区间进行操作,首先我要把splay树翻成上图所示的样子,翻成这个样子以后可以知道,*所表示的那个子树,就是区间a[a , a + 1 , ... , b],所以说我的操作都是对节点b+1的左子树进行的。

    值得一提的是,我怎么才能在splay中找到a-1和b+1节点呢?方法是我对每个节点x维护一个size[x],表示x子树的大小为size[x],这样一来我就可以通过的函数find_k递归找到整棵树中对应数列中a[k]这个数的节点编号。

   最后,无论对区间a[l , ... , r]增删改查插入翻转,其实现时都按照下列步骤:

  ①找到a[l - 1]对应节点x,通过splay操作,翻到根上去;找到a[r + 1]对应节点y,通过splay操作,一直翻到y成为x的儿子;

  ②对y的左子节点进行相应的操作(增删改查等)

  ③使用update函数更新x、y、以及y的左子儿子

因此,在做题的时候,要抄板的地方有rotate函数、splay函数、find_k函数、build_tree函数、set_root函数,另外不要忘了一上来插入两个无实际意义的节点,然后在他们之间进行build_tree操作

     

1500: [NOI2005]维修数列

Time Limit: 10 Sec  Memory Limit: 64 MB
Submit: 13919  Solved: 4479
[Submit][Status][Discuss]

Description

Input

输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目。
第2行包含N个数字,描述初始时的数列。
以下M行,每行一条命令,格式参见问题描述中的表格。
任何时刻数列中最多含有500 000个数,数列中任何一个数字均在[-1 000, 1 000]内。
插入的数字总数不超过4 000 000个,输入文件大小不超过20MBytes。

Output

对于输入数据中的GET-SUM和MAX-SUM操作,向输出文件依次打印结果,每个答案(数字)占一行。

Sample Input

9 8
2 -6 3 5 1 -5 -3 6 3
GET-SUM 5 4
MAX-SUM
INSERT 8 3 -5 7 2
DELETE 12 1
MAKE-SAME 3 3 2
REVERSE 3 6
GET-SUM 5 4
MAX-SUM

Sample Output

-1
10
1
10

#pragma warning(disable:4786)#pragma comment(linker, "/STACK:102400000,102400000")#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<stack>#include<queue>#include<map>#include<set>#include<vector>#include<cmath>#include<string>#include<sstream>#include<bitset>#define LL long long#define FOR(i,f_start,f_end) for(int i=f_start;i<=f_end;++i)#define mem(a,x) memset(a,x,sizeof(a))#define lson l,m,x<<1#define rson m+1,r,x<<1|1using namespace std;const int INF = 0x3f3f3f3f;const int mod = 1e9 + 7;const double PI = acos(-1.0);const double eps=1e-6;const int maxn = 5e5 + 1000;int fa[maxn] , ch[maxn][2] , w[maxn] , size[maxn] , lmax[maxn] , rmax[maxn] , ans[maxn] , sum[maxn];//w[i]是节点i中数的大小,size[i]是节点i为根的子树的大小,lmax\rmax分别是节点i为根的子树对应的序列中从左端点开始//的连续最大值,以及右端点开始的连续最大值,ans[i]是节点i为根的树对应序列的一段和最大的连续区间的和bool rev[maxn] , tag[maxn];     //rev[i]是节点i为根子树是否被翻转,tag[i]是节点i为根子树是否被全部修改权值int a[maxn] , st[maxn];     //st中存放回收的节点char s[20];int n , m , tot , root , top;       //root为伸展树的根,top为st的栈顶指针int newnode()       //拿出一个新的节点{    int num;    if(top)        num = st[top--];    else        num = ++tot;    ch[num][0] = ch[num][1] = fa[num] = 0;    tag[num] = rev[num] = 0;    size[num] = 1;    sum[num] = w[num] = rmax[num] = lmax[num] = -INF;      //如果区间伸展树维护的是其他值,这里要酌情修改    return num;}void update(int x)      //相当于线段树的pushup功能{    if(!x)      return ;    size[x] = size[ch[x][0]] + size[ch[x][1]] + 1;    sum[x] = sum[ch[x][0]] + sum[ch[x][1]] + w[x];    lmax[x] = max(lmax[ch[x][0]] , sum[ch[x][0]] + w[x] + max(0 , lmax[ch[x][1]]));        //这些都要根据伸展树维护的内容酌情修改    rmax[x] = max(rmax[ch[x][1]] , sum[ch[x][1]] + w[x] + max(0 , rmax[ch[x][0]]));    ans[x] = max(max(ans[ch[x][0]] , ans[ch[x][1]]) , max(0 , rmax[ch[x][0]]) + w[x] + max(0 , lmax[ch[x][1]]));}void reverse(int x)    //将节点x为根子树对应的序列翻转{    if(!x)      return ;    swap(lmax[x] , rmax[x]);    swap(ch[x][0] , ch[x][1]);    rev[x] ^= 1;}void replace(int x , int d)     //将节点x为根子树对应序列中所有数置为d{    if(!x)      return;    w[x] = d;   sum[x] = d * size[x];    lmax[x] = rmax[x] = ans[x] = max(d , d * size[x]);    tag[x] = 1;     rev[x] = 0;}void push_down(int x){    if(rev[x]){        if(ch[x][0])        reverse(ch[x][0]);        if(ch[x][1])        reverse(ch[x][1]);        rev[x] = 0;    }    if(tag[x]){        if(ch[x][0])        replace(ch[x][0] , w[x]);        if(ch[x][1])         replace(ch[x][1] , w[x]);        tag[x] = 0;    }}int dir(int x)      //若节点x是其父亲节点的左子节点,返回0;是右子节点,返回1{    return x == ch[fa[x]][1];}void rotate(int x){    int y , z , a , b , c;    y = fa[x];      z = fa[y];      b = dir(x);     a = ch[x][!b];    if(z == 0)        root = x;    else{        c = dir(y); ch[z][c] = x;    }    fa[x] = z;  fa[y] = x;  ch[x][!b] = y;  ch[y][b] = a;    if(a)       fa[a] = y;    update(y);      update(x);      //因为节点x和节点y为根子树中的节点改变了,所以要update}void down(int x)     //将x到root的所有节点信息更新{    if(fa[x])       down(fa[x]);    push_down(x);}void splay(int x , int i)      //将x翻成节点i的直接子节点{    down(x);            //现在要将x向上翻了,所以先把他到root的内容更新    int y , z , b , c;    while(fa[x] != i){        y = fa[x];      z = fa[y];        if(z == i)      rotate(x);        else{            b = dir(x);     c = dir(y);            if(b ^ c){                                  //“之”字型                rotate(x);  rotate(x);            }            else{                                       //“一”字型                rotate(y);  rotate(x);            }        }    }}int find_k(int x , int k)         //在节点x为根的树对应的序列中,找第k个元素对应的值{    push_down(x);           //find_k的同时正好随手push_down    if(size[ch[x][0]] == k - 1)     return x;    if(size[ch[x][0]] > k - 1)      return find_k(ch[x][0] , k);    else    return find_k(ch[x][1] , k - size[ch[x][0]] - 1);}void build_tree(int l , int r , int tt)     //把一个序列a[l , l + 1 , ... , r]建成以节点tt为根的树{    int mid = l + (r - l) / 2;    w[tt] = a[mid];    if(l == r){        sum[tt] = lmax[tt] = rmax[tt] = ans[tt] = w[tt] ;       size[tt] = 1;        return;    }    if(l < mid){        ch[tt][0] = newnode();      fa[ch[tt][0]] = tt;     build_tree(l , mid - 1 , ch[tt][0]);    }    if(mid < r){        ch[tt][1] = newnode();      fa[ch[tt][1]] = tt;     build_tree(mid + 1 , r , ch[tt][1]);    }    update(tt);}void erase(int x){    if(!x)      return ;    st[++top] = x;    if(ch[x][0])        erase(ch[x][0]);    if(ch[x][1])        erase(ch[x][1]);}int Query(int l , int num)      //查询题目当前数列[l , l + 1 , ... , l + num - 1]的和{    int x = find_k(root , l);       splay(x , 0);      //为什么在root里找第l大数而不是l-1大?别忘了一开始插入了-INF节点    int y = find_k(ch[x][1] , num + 1);     splay(y , x);    return sum[ch[y][0]];}void Insert(int l , int num){    for(int i = 1 ; i <= num ; i++){        scanf("%d" , &a[i]);    }    int x = find_k(root , l + 1);       splay(x , 0);    int y = find_k(ch[x][1] , 1);         splay(y , x);    ch[y][0] = newnode();   fa[ch[y][0]] = y;    build_tree(1 , num , ch[y][0]);    update(y);      update(x);}void Delete(int l , int num)        //删除题目当前数列中的[l , l + 1 , ... , l + num - 1]{    int x = find_k(root , l);       splay(x , 0);    int y = find_k(ch[x][1] , num + 1);     splay(y , x);    erase(ch[y][0]);    fa[ch[y][0]] = 0;       ch[y][0] = 0;    update(y);  update(x);}void Reverse(int l , int num){    int x = find_k(root , l);       splay(x , 0);    int y = find_k(ch[x][1] , num + 1);     splay(y , x);    reverse(ch[y][0]);    update(y);  update(x);}void Replace(int l , int num , int d){    int x = find_k(root , l);       splay(x , 0);    int y =find_k(ch[x][1] , num + 1);      splay(y , x);    replace(ch[y][0] , d);    update(y);      update(x);}void set_root(){    top = 0;    lmax[0] = rmax[0] = ans[0] = -INF;    tot = 2;    root = 1;    fa[1]= 0;   size[1] = 2;  ch[1][1] = 2; w[1] = sum[1] = lmax[1] = rmax[1] = -INF;     //插入两个无实际意义的节点,以防止使用上面函数时越界    fa[2] = 1;  size[2] = 1;  w[2] = sum[2] = lmax[2] = rmax[2] = -INF;}int main(){    int p , tot , val;    scanf("%d %d" , &n , &m);    for(int i = 1 ; i <= n ; i++){        scanf("%d" , &a[i]);    }    set_root();    ch[2][0] = newnode();       fa[ch[2][0]] = 2;       //把初始序列插在两个占坑用的节点中间很关键    build_tree(1 , n , ch[2][0]);    update(2);      update(1);    for(int _ = 0 ; _ < m ; _++){        scanf("%s" , s);        if(s[0] == 'I'){            scanf("%d %d" , &p , &tot);            Insert(p , tot);        }        else if(s[0] == 'D'){            scanf("%d %d" , &p , &tot);            Delete(p , tot);        }        else if(s[0] == 'M'){            if(s[2] == 'K'){                scanf("%d %d %d" , &p , &tot , &val);                Replace(p , tot , val);            }            else{                printf("%d\n" , ans[root]);            }        }        else if(s[0] == 'R'){            scanf("%d %d" , &p , &tot);            Reverse(p , tot);        }        else if(s[0] == 'G'){            scanf("%d %d" , &p , &tot);            int res = Query(p , tot);            printf("%d\n" , res);        }    }    return 0;}


0 0