POJ 3468 A Simple Problem with Integers Splay tree&Segment tree
N年前用线段树做的,比较简单,可以当作线段树懒惰标记的练习。
重新用Splay tree写,有点小题大作,而且代码长,效率低,不过当作Splay练手不错。
区间处理,也是splay的强项。一开始建树的时候,将键值设为下标,插入到树中,保证有序,总是出错。
后来采用HH的做法,直接建树,中间结点为父结点,区间左端为左子树,区间右端为右子树,这样就保证始终有序,之后的旋转不改变。
Splay的区间处理,对于[l,r],将l-1旋转至根,将r+1旋转至根的右孩子,那么根的右孩子的左子树就全为[l,r]中的结点,便可以对区间进行有效处理。
同样区间更新用到懒惰标记。
区间可能靠近两端点,当减1加1时可能溢出,不方便处理,可以在两端加入冗余结点。
以下为Splay tree
[cpp]
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 100005
#define inf 1<<29
#define LL long long
#define Key_value ch[ch[root][1]][0]
using namespace std;
int pre[N],key[N],ch[N][2],root,tot1; //分别表示父结点,键值,左右孩子(0为左孩子,1为右孩子),根结点,结点数量
int size[N],s[N],tot2,val[N]; //分别表示子树规模,内存池,内存池容量
int add[N];
LL sum[N]; //延迟标记,子树结点和
int n,q;
//debug部分copy from hh
void Treaval(int x) {
if(x) {
Treaval(ch[x][0]);
printf("结点%2d:左儿子 %2d 右儿子 %2d 父结点 %2d size = %2d ,val = %2d , sum = %2d \n",x,ch[x][0],ch[x][1],pre[x],size[x],val[x],sum[x]);
Treaval(ch[x][1]);
}
}
void debug() {printf("%d\n",root);Treaval(root);}
//以上Debug
//新建一个结点
void NewNode(int &r,int father,int k){
if(tot2)
r=s[--tot2];
else
r=++tot1;
pre[r]=father;
val[r]=k;
sum[r]=k;
add[r]=0;
size[r]=1;
ch[r][0]=ch[r][1]=0; //左右孩子为空
}
//将延迟标记更新到孩子结点
void Push_Down(int r){
if(add[r]){
val[r]+=add[r];
add[ch[r][0]]+=add[r];
add[ch[r][1]]+=add[r];
sum[ch[r][0]]+=(LL)add[r]*size[ch[r][0]];
sum[ch[r][1]]+=(LL)add[r]*size[ch[r][1]];
add[r]=0;
}
}
//通过孩子结点更新父结点
void Push_Up(int r){
size[r]=size[ch[r][0]]+size[ch[r][1]]+1;
sum[r]=sum[ch[r][0]]+sum[ch[r][1]]+val[r]+add[r];
}
//旋转,kind为1为右旋,kind为0为左旋
void Rotate(int x,int kind){
int y=pre[x];
Push_Down(x);
Push_Down(y);
//类似SBT,要把其中一个分支先给父节点
ch[y][!kind]=ch[x][kind];
pre[ch[x][kind]]=y;
//如果父节点不是根结点,则要和父节点的父节点连接起来
if(pre[y])
ch[pre[y]][ch[pre[y]][1]==y]=x;
pre[x]=pre[y];
ch[x][kind]=y;
pre[y]=x;
Push_Up(y);
}
//Splay调整,将根为r的子树调整为goal
void Splay(int r,int goal){
Push_Down(r);
while(pre[r]!=goal){
//父节点即是目标位置,goal为0表示,父节点就是根结点
if(pre[pre[r]]==goal)
Rotate(r,ch[pre[r]][0]==r);
else{
int y=pre[r];
int kind=ch[pre[y]][0]==y;
//两个方向不同,则先左旋再右旋
if(ch[y][kind]==r){
Rotate(r,!kind);
Rotate(r,kind);
}
//两个方向相同,相同方向连续两次
else{
Rotate(y,kind);
Rotate(r,kind);
}
}
}
Push_Up(r);
//更新根结点
if(goal==0) root=r;
}
//把第k位的数转到goal下边
void RotateTo(int k,int goal) {
int r=root;
Push_Down(r);
while(size[ch[r][0]]!=k){
if(k<size[ch[r][0]]){
r=ch[r][0];
} else {
k-=(size[ch[r][0]]+1);
r=ch[r][1];
}
Push_Down(r);
}
Splay(r,goal);
}
int Insert(int k){
int r=root;
while(ch[r][key[r]<k])
r=ch[r][key[r]<k];
NewNode(ch[r][k>key[r]],r,k);
//将新插入的结点更新至根结点
//Push_Up(r);
Splay(ch[r][k>key[r]],0);
return 1;
}
//找前驱,即左子树的最右结点
int get_pre(int x){
int tmp=ch[x][0];
if(tmp==0) return inf;
while(ch[tmp][1])
tmp=ch[tmp][1];
return key[x]-key[tmp]; 补充:软件开发 , C++ ,