伸展树看了几天了,总算是摸着点方向,只能说这真的是神一样的数据结构,各种延迟标记……
参考了很多大牛的博客,代码的基本写法从网上挑了一种比较好理解的开始模仿。
这里有一个模板和对一道题的分析,一开始我模仿的就是这个,最后发现指针还是有点不习惯……最后我会贴出用这个模板解这题的代码。
这个模板是少数这道题可以跑进1s内的,不过利用数组建树那部分似乎有bug:
Splay Tree 标签_51CTO技术博客
以下链接是网上关于Splay的总结,里面有许多题解……这些都给了我很大的帮助!
cxlove的,网上很多人都是他的写法修改的:伸展树(Splay tree)学习小结 - 窝是爱酱,喵~~~~ - 博客频道 - CSDN.NET
kuangbin的,我正在模仿他的写法:Splay Tree(伸展树总结) - kuangbin - 博客园
今年8月写了400多博文的 haha593572013 的:无比强大的数据结构 伸展树总结 - 鲜花会有的,AC会有的! - 博客频道 - CSDN.NET
FZU_Jason的,还没有仔细看:【总结】伸展树 - DrunBee - 博客园
这道题的题意和分析在上面几篇博客里都有,我就不写了。
#include <cstdio>
#include <algorithm>
using namespace std;
#define min(a,b) ((a)<(b)?(a):(b))
#define Key_value ch[ch[root][1]][0] //根节点右孩子的左孩子
#define Key_Value ch[ch[root][1]][0]
const int N=200010;
const int INF=0x3f3f3f3f;
//分别表示父结点,左右孩子(0为左孩子,1为右孩子),键值,结点规模
int pre[N],ch[N][2],key[N],size[N];
//加法延迟标记,翻转延迟标记,最小值
int add[N],rev[N],m[N];
int root,tot1; //根节点,节点数量
int s[N],tot2;//内存池、内存池容量
int data[N]; //初始数列数组
int n,q;
void NewNode (int &r,int father,int k)
{
if (tot2)r=s[tot2--];
else r=++tot1;
ch[r][0]=ch[r][1]=0;
pre[r]=father;
size[r]=1;
add[r]=rev[r]=0;
key[r]=m[r]=k;
}
void Update_Rev (int r) //更新r节点的翻转值
{
if(!r)return;
swap(ch[r][0],ch[r][1]);
rev[r]^=1;
}
void Update_Add (int r,int ADD) //更新r节点的加法值
{
if(!r)return;
add[r]+=ADD;
key[r]+=ADD;
m[r]+=ADD;
}
void Push_Up (int r) //向上更新,由r节点的儿子更新r节点
{
size[r]=size[ch[r][0]]+size[ch[r][1]]+1;
m[r]=key[r];
if(ch[r][0])m[r]=min(m[r],m[ch[r][0]]);
if(ch[r][1])m[r]=min(m[r],m[ch[r][1]]);
}
void Push_Down (int r) //向下更新,由r节点更新其儿子节点
{
if (rev[r])
{
Update_Rev(ch[r][0]);
Update_Rev(ch[r][1]);
rev[r]=0;
}
if (add[r])
{
Update_Add(ch[r][0],add[r]);
Update_Add(ch[r][1],add[r]);
add[r]=0;
}
}
void Build (int &x,int l,int r,int father)
{//利用数组data建树,data从1开始,有n个元素
//调用格式 Build(Key_value,1,n,ch[root][1]);
if (l>r) return;
int mid=(l+r)>>1;
NewNode(x,father,data[mid]);
Build(ch[x][0],l,mid-1,x);
Build(ch[x][1],mid+1,r,x);
Push_Up(x);
}
void Init ()
{//Splay初始化
root=tot1=tot2=0;
ch[root][0]=ch[root][1]=size[root]=add[root]=rev[root]=pre[root]=0;
m[root]=INF;//这个不用也可以,如果在push_up那判断了的话,否则需要
NewNode(root,0,INF);
NewNode(ch[root][1],root,INF);
Push_Up(ch[root][1]);
Push_Up(root);
}
void Rotate (int x,int kind)
{//旋转
int y=pre[x];
Push_Down(y);
Push_Down(x);
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);
}
void Splay (int r,int goal)
{//Splay调整,将第r节点调整到goal位置
Push_Down(r);
while(pre[r]!=goal)
{
if(pre[pre[r]]==goal)
{
//这题有反转操作,需要先push_down,再判断左右孩子
Push_Down(pre[r]);
Push_Down(r);
Rotate(r,ch[pre[r]][0]==r);
}
else
{
//这题有反转操作,需要先push_down,再判断左右孩子
Push_Down(pre[pre[r]]);
Push_Down(pre[r]);
Push_Down(r);
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;
}
int Get_Kth (int r,int k) //找第k个节点
{
Push_Down(r);
int t=size[ch[r][0]]+1;
if(t==k)return r;
if(t>k)return Get_Kth(ch[r][0],k);
else return Get_Kth(ch[r][1],k-t);
}
int Get_Min (int r)
{
Push_Down(r);
while (ch[r][0])
{
r=ch[r][0];
Push_Down(r);
}
return r;
}
int Get_Max (int r)
{
Push_Down (r);
while (ch[r][1])
{
r=ch[r][1];
Push_Down(r);
}
return r;
}
//下面是操作了
void Add (int l,int r,int D)
{//区间l到r全部加D
Splay(Get_Kth(root,l),0);
Splay(Get_Kth(root,r+2),root);
Update_Add(Key_value,D);
Push_Up(ch[root][1]);
Push_Up(root);
}
void Reverse (int l,int r)
{//区间l到r全部翻转
Splay(Get_Kth(root,l),0);
Splay(Get_Kth(root,r+2),root);
Update_Rev(Key_value);
Push_Up(ch[root][1]);
Push_Up(root);
}
void Revolve (int l,int r,int T)
{//循环右移T长度,其实就是交换区间
int len=r-l+1;
T=(T%len+len)%len;
if (T==0)return;
int c=r-T+1;//将区间[c,r]放在[l,c-1]前面
Splay(Get_Kth(root,c),0);
Splay(Get_Kth(root,r+2),root);
int tmp=Key_value;
Key_value=0; //此时已将区间拆下
Push_Up(ch[root][1]); //拆下后记得更新
Push_Up(root);
Splay(Get_Kth(root,l),0);
Splay(Get_Kth(root,l+1),root); //放在l之前
Key_value=tmp;
pre[Key_value]=ch[root][1];//这个不要忘记
Push_Up(ch[root][1]);
Push_Up(root);
}
void Insert (int x,int P)
{//在第x个数后面插入P
Splay(Get_Kth(root,x+1),0);
Splay(Get_Kth(root,x+2),root);
NewNode(Key_value,ch[root][1],P);
Push_Up(ch[root][1]);
Push_Up(root);
}
void erase (int r)
{//回收内存
if(r)
{
s[++tot2]=r;
erase(ch[r][0]);
erase(ch[r][1]);
}
}
void Delete (int x)
{//删除第x个数
Splay(Get_Kth(root,x),0);
Splay(Get_Kth(root,x+2),root);
erase(Key_value);
pre[Key_value]=0;
Key_value=0;
Push_Up(ch[root][1]);
Push_Up(root);
}
int Query_Min (int l,int r)
{//查找区间最小
Splay(Get_Kth(root,l),0);
Splay(Get_Kth(root,r+2),root);
return m[Key_value];
}
int main ()
{
#ifdef ONLINE_JUDGE
#else
freopen("read.txt","r",stdin);
#endif
int n,m,i;
char str[10];
scanf("%d",&n);
Init();
for (i=1;i<=n;i++)
scanf("%d",&data[i]);
Build(Key_value,1,n,ch[root][1]);
scanf("%d",&m);
int x,y,tmp;
while (m--)
{
scanf("%s",str);
switch (str[0])
{
case 'A':
scanf("%d%d%d", &x, &y, &tmp);
Add(x,y,tmp);
break;
case 'R':
if ('E' == str[3])
{
scanf("%d%d", &x, &y);
Reverse(x, y);
}
else
{
scanf("%d%d%d", &x, &y, &tmp);
Revolve(x, y, tmp);
}
break;
case 'I':
scanf("%d%d", &x, &tmp);
Insert(x,tmp);
break;
case 'D':
scanf("%d", &x);
Delete(x);
break;
case 'M':
scanf("%d%d", &x, &y);
printf("%d\n",Query_Min(x,y));
break;
}
}
return 0;
}
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#define max(a,b) ((a)>(b)?(a):(b))
#define max3(a,b,c) (max(a,max(b,c)))
#define min(a,b) ((a)<(b)?(a):(b))
#define min3(a,b,c) (min(a,min(b,c)))
using namespace std;
const int INF=0x7fffffff;
const int MAXPT=1000005;
class SPLAYTREE
{
public:
struct Node
{
int key,minv,size,add;//键值,区间最小值,子树规模,相加延迟标记
bool rev; //翻转标记
Node *left,*right,*father;
Node (){}
Node(int _key):key(_key){minv=_key,size=1,add=0,rev=false;}
}SPLAY[MAXPT],*SP,*root,*head,*tail;
void init () //初始化一棵伸展树,只有head和tail结点
{//tail.size=1,head.size=1(初始)而且保证以后接入的结点:Node.size>=head.size
SP=SPLAY;
SPLAY->key=SPLAY->minv=INF,SPLAY->size=0;SPLAY->rev=false;SPLAY->add=0;
SPLAY->left=SPLAY->right=SPLAY->father=SPLAY;
head=new(++SP)Node(INF);
head->left=head->right=head->father=SPLAY;
tail=new(++SP)Node(INF);
tail->left=tail->right=tail->father=SPLAY;
head->right=tail,tail->father=head,head->size++;
root=head;//最开始,root的值为head,head和tail是固定不变的,而root的指向的地址是会变化的
}
void pushdown (Node *&t)//延迟操作的经典代码:把当前的标记下推一个节点
{
if (t->rev)
{//如果区间需要旋转
swap(t->left,t->right);
t->left->rev=!t->left->rev;
t->right->rev=!t->right->rev;
t->rev=false;
}
if (t->add)
{//如果区间的值要加add
if(t->left!=SPLAY){
t->left->key+=t->add;
t->left->minv+=t->add;
t->left->add+=t->add;
}
if(t->right!=SPLAY){
t->right->key+=t->add;
t->right->minv+=t->add;
t->right->add+=t->add;
}
t->add=0;
}
}
void pushup (Node *&t)//更新区间的最小值和区间元素的个数
{
t->size=t->left->size+t->right->size+1;
t->minv=min3(t->key,t->left->minv,t->right->minv);
}
void zig (Node *&t)
{ //右旋转操作
Node *f=t->father,*r=t->right;
pushdown(f->right);
pushdown(t->left);
pushdown(t->right);
t->father=f->father;
if (f==root) root=t;
else{
if(f->father->left==f) f->father->left=t;
else f->father->right=t;
}
t->right=f,r->father=f,f->father=t,f->left=r;
pushup(f);pushup(t);
}
void zag (Node *&t)
{ //左旋转操作
Node *f=t->father,*l=t->left;
pushdown(f->left);
pushdown(t->left);
pushdown(t->right);
t->father=f->father;
if(f==root) root=t;
else{
if(f->father->left==f) f->father->left=t;
else f->father->right=t;
}
t->left=f,l->father=f,f->father=t,f->right=l;
pushup(f);pushup(t);
}
void splay (Node *&root,Node *&t)
{// 伸展树的核心:伸展操作
pushdown(t);
while (root!=t)
{
if(t->father==root){
if(t->father->left==t) zig(t);
else zag(t);//单旋转
}
else{
if(t->father->father->left==t->father){
if(t->father->left==t) zig(t->father),zig(t);//一字旋转
else zag(t),zig(t);//之字旋转
}else{
if(t->father->left==t) zig(t),zag(t);//之字旋转
else zag(t->father),zag(t);//一字旋转
}
}
}
}
// 建树 /
Node* build (Node *father,int *a,int ll,int rr) //返回该树根节点
{ //仿线段树建树方式建立一个splay树,与build_tree合用
if(ll>rr)return SPLAY;
int mid=(ll+rr)>>1;
SP=SP+mid;
Node *t;
t=new(SP)Node(a[mid]);
t->father=father;
t->left=build(t,a,ll,mid-1);
t->right=build(t,a,mid+1,rr);
pushup(t);
return t;
}
/*
* function: 把数组a建立成一棵伸展树。采用的建树方式与线段树的建树方式相同
*
* @param:root 新建立树的根
* @param: a 建树用到的数组,0位置不存元素
* @param: n 数组a中元素的个数
* */
void build_tree (Node *&t,int *a,int n)
{
int ll=1,rr=n;
int mid=(ll+rr)>>1;
SP=SP+mid;
t=new(SP)Node(a[mid]);
t->father=SPLAY;
t->left=build(t,a,ll,mid-1);
t->right=build(t,a,mid+1,rr);
pushup(t);
}
void insert (int key,int pos)
{// 插入一个值到指定的位置
Node *t=new(++SP)Node(key);
t->left=t->right=t->father=SPLAY;
Node *r=root,*p;
bool flag=false;//默认插入树的左孩子上
while(pushdown(r),r!=SPLAY){
p=r,r->size++;
if(r->left->size+1>pos)r=r->left,flag=false;
else pos-=r->left->size+1,r=r->right,flag=true;
}
if(flag) p->right=t;
else p->left=t;
t->father=p;
splay(root,t);
}
// 建树 /
void select (Node *&root,int pos)
{// 把pos位置的元素旋转到根结点(或任意*&root所指的位置)
Node *r=root;
while (pushdown(r),r->left->size+1!=pos){
if(r->left->size+1>pos) r=r->left;
else pos-=r->left->size+1,r=r->right;
}
splay(root,r);
}
void insert_k (int pos,int *a,int n) //函数有bug!!!!!!!
{ //把a中的n个数插入数组中,a的0号位不用
Node *t;
build_tree(t,a,n);
select(root,pos);
select(root->right,1);
root->right->left=t;
t->father=root->right;
pushup(root->right);
pushup(root);
splay(root,t);
}
void remove (int pos)
{// 把pos位置的元素删除
select(root,pos);
if(root->left==SPLAY) root=root->right;
else if(root->right==SPLAY) root=root->left;
else{
select(root->left,root->left->size);
root->left->right=root->right;
root->right->father=root->left;
root=root->left;
}
root->father=SPLAY;
pushup(root);
}
void remove_k (int ll,int rr)
{ //删除区间[l,r]
//确定区间
select(root,ll);
select(root->right,rr-ll);
//执行删除操作
root->right->left=SPLAY;
pushup(root->right);
pushup(root);
}
void plus (int l,int r,int a)
{//区间[l,r]同时加上a
//确定区间
select(root,l);
select(root->right,r-l);
//执行操作
Node *t=root->right->left;
t->add+=a,t->key+=a,t->minv+=a;
splay(root,t); //记得splay
}
void reverse (int l,int r)
{// 翻转区间[l,r]
select(root,l);
select(root->right,r-l);
Node *t=root->right->left;
t->rev=!t->rev;
splay(root,t); //记得splay
}
void revolve (int l,int r,int a)
{// 区间[l,r]循环右移a次,revolve l r a 其实就是交换(l,r-a)和(r-a+1,r)两个区间。
if (a%(r-2-l+1)) //传递过来的参数与带求差2
{
a=a%(r-2-l+1);
select(root,l);
select(root->right,r-l);
select(root->right->left,root->right->left->size-a);
select(root->right->left->right,root->right->left->right->size);
Node *p=root->right->left,*t=root->right->left->right;
p->right=SPLAY;
p->father->left=t,t->father=p->father;
t->right=p,p->father=t;
pushup(t);pushup(p);
splay(root,p); //} //记得splay
}
}
int query (int l,int r)
{ //查询区间的最小值
select(root,l);
select(root->right,r-l);
return root->right->left->minv;
}
void inorder (Node *t)
{ //从结点t开始 中序遍历树
pushdown(t);
if (t->left!=SPLAY)
inorder(t->left);
if (t->key!=INF)printf("%d ",t->key);
if (t->right!=SPLAY)
inorder(t->right);
}
}tree;
int l,r,x,pos;
char str[10];
int data[100000+10];
int main ()
{
#ifdef ONLINE_JUDGE
#else
freopen("read.txt","r",stdin);
#endif
int n,m,i;
scanf("%d",&n);
tree.init();
for (i=1;i<=n;i++)
scanf("%d",&data[i]);//,tree.insert(data[i],i);
tree.insert_k(1,data,n);
scanf("%d",&m);
int x,y,tmp;
while (m--)
{
scanf("%s",str);
switch (str[0])
{
case 'A':
scanf("%d%d%d", &x, &y, &tmp);
y+=2;
tree.plus(x,y,tmp);
break;
case 'R':
if ('E' == str[3])
{
scanf("%d%d", &x, &y); x,y+=2;
tree.reverse(x, y);
}
else
{
scanf("%d%d%d", &x, &y, &tmp); x,y+=2;
tree.revolve(x, y, tmp);
}
break;
case 'I':
scanf("%d%d", &x, &tmp); x++;
tree.insert(tmp,x);
break;
case 'D':
scanf("%d", &x); x++;
tree.remove(x);
break;
case 'M':
scanf("%d%d", &x, &y); x,y+=2;
int ans=tree.query(x, y);
printf("%d\n",ans);
break;
}
}
return 0;
}
更多推荐
Splay伸展树学习小记 Poj 3580 SuperMemo
发布评论