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 MBSubmit: 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
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
-110
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;}
- bzoj1500 修改数列 区间splay树讲解
- BZOJ1500维修数列Splay
- 【bzoj1500】维修数列 splay
- bzoj1500维修数列splay
- splay BZOJ1500 NOI2005 维护数列
- [BZOJ1500][NOI2005][Splay]维修数列
- 【BZOJ1500】[NOI2005]维修数列 Splay
- 【splay】[noi2005] bzoj1500 维修数列
- bzoj1500 [NOI2005]维修数列 splay
- [BZOJ1500]NOI2005 维修数列|splay
- 【bzoj1500】[NOI2005]维修数列 Splay
- 【bzoj1500】[NOI2005]维修数列 Splay
- 【BZOJ1500】[NOI2005]维修数列【Splay】
- BZOJ1500 [NOI2005]维修数列(Splay)
- [NOI2005] [BZOJ1500] 维修数列 - splay
- BZOJ1500: [NOI2005]维修数列 Splay
- [Splay] BZOJ1500: [NOI2005]维修数列
- 【Splay】BZOJ1500 [NOI2005]维修数列
- 部署在阿里云上的Django项目中,有的图片能显示出来,有的图片显示不出来的原因
- Matlab图像处理(01)-Matlab基础
- hrbust 1012
- Wordpress特定文章列表的调用代码
- 数据库三大范式
- bzoj1500 修改数列 区间splay树讲解
- 前端学习的结业作业
- MySQL对查询结果排序
- opengl画会动的木头人(stick man)
- Hadoop之—— WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform...
- 交换排序——冒泡排序
- 数据结构(c++)(3)--简单的计算器
- 3DES和SHA-1
- svg中path标签的用法