仅以此篇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|1
using 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;
}