一、文艺二叉树(来源:codevs 3303)
有n个数,这个序列依次是(1,2,…,n-1,n),每次翻转区间(l,r),输出翻转的最终结果。
思路:
1、用树的话,如何做到区间反转?把需要反转的树放到一棵子树当中,令该子树的左右孩子对换即可;
2、很自然,会想到用layz的思路优化时间;
3、如果左右子树对调,这还是一棵“左<根<右”的树吗?显然,它完全不合。这使得这棵树不能用原先的findip,因为它不符“左<根<右”;所以,我们可以用findshuzi的思路来查询它的ip。线段树的操作splay不受影响。
4、此题建树时才用了建伸展树的方法,为的是让树一开始达到最平衡的情况。如果执意要用旧版,也没问题。建树还有一点需要注意,它多建了两棵树head和tail,为的是能更方便操作。
总结:这题打破了以往对伸展树的定义,成了一棵没有大小之分的伸展树,它利用“伸展树无论怎样翻转,中序(前序、后序)遍历不变”的性质,用findshuzi()的思想来查询值为x的中序遍历位置。所以,即使是不能按大小顺序排列的数据,也可以考虑伸展树;换句话说,伸展树也可以记录没有大小之分的数据。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int root;
int a[100010];
struct node
{
int c,f,d,son[2];
bool fz;
}tr[100010];int len=0;
void weihufz(int x)
{
tr[x].fz=false;
swap(tr[x].son[0],tr[x].son[1]);
tr[tr[x].son[0]].fz^=1;
tr[tr[x].son[1]].fz^=1;
}
void update(int x)
{
tr[x].c=1+tr[tr[x].son[0]].c+tr[tr[x].son[1]].c;
}
void rotate(int x,int w)
{
int f=tr[x].f;
int ff=tr[f].f;
int r,R;
R=f;r=tr[x].son[w];
tr[R].son[1-w]=r;
if(r!=0) tr[r].f=R;
R=ff;r=x;
if(tr[ff].son[0]==f) tr[R].son[0]=r;
else tr[R].son[1]=r;
tr[r].f=R;
R=x;r=f;
tr[R].son[w]=r;
tr[r].f=R;
update(f);
update(x);
}
void splay(int x,int rt)
{
while(tr[x].f!=rt)
{
int f=tr[x].f;
int ff=tr[f].f;
if(rt==ff)
{
if(tr[x].fz==true) weihufz(x);
if(tr[f].son[0]==x) rotate(x,1);
else rotate(x,0);
}
else
{
if(tr[f].fz==true) weihufz(f);//父亲先翻转
if(tr[x].fz==true) weihufz(x);//儿子再翻转
if(tr[ff].son[0]==f&&tr[f].son[0]==x){rotate(f,1);rotate(x,1);}
else if(tr[ff].son[1]==f&&tr[f].son[1]==x){rotate(f,0);rotate(x,0);}
else if(tr[ff].son[0]==f&&tr[f].son[1]==x){rotate(x,0);rotate(x,1);}
else if(tr[ff].son[1]==f&&tr[f].son[0]==x){rotate(x,1);rotate(x,0);}
}
}
if(rt==0) root=x;
}
int ins(int l,int r)
{
if(l>r) return 0;
len++;int now=len;
int mid=(l+r)/2;
int lc=ins(l,mid-1),rc=ins(mid+1,r);
if(lc!=0) tr[lc].f=now;
if(rc!=0) tr[rc].f=now;
tr[now].d=mid;tr[now].fz=false;
tr[now].c=tr[lc].c+tr[rc].c+1;
tr[now].son[0]=lc;tr[now].son[1]=rc;
return now;
}
int findip(int k)//findip 指的是找中序遍历第k的数的地址
{
int x=root;
while(1)
{
if(tr[x].fz==true) weihufz(x);
int lc=tr[x].son[0],rc=tr[x].son[1];
if(k<=tr[lc].c) x=lc;
else if(k>tr[lc].c+1){k-=tr[lc].c+1;x=rc;}
else break;
}
return x;
}
void fanzhuan(int l,int r)
{
int lc=findip(l-1),rc=findip(r+1);
splay(lc,0);splay(rc,lc);
int x=tr[rc].son[0];
tr[x].fz^=1;
}
void dfs(int x)//中序遍历
{
if(tr[x].fz==true) weihufz(x);
int lc=tr[x].son[0],rc=tr[x].son[1];
if(lc!=0) dfs(lc);//左
a[++len]=tr[x].d;//根
if(rc!=0) dfs(rc);//右
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
root=ins(0,n+1);
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
l++;r++;//+1是因为受到值为0的节点的影响
fanzhuan(l,r);
}
len=0;
dfs(root);
for(int i=2;i<len;i++) printf("%d ",a[i])//i=1是head i=len是tail
return 0;
}
二、二逼平衡树(来源:bzoj 3196)
有n个数,需要提供以下几种操作:
1.查询k在区间内的排名
2.查询区间内排名为k的值
3.修改pos位置上的数值为k
4.查询k在区间内的前驱(前驱定义为小于x,且最大的数)
5.查询k在区间内的后继(后继定义为大于x,且最小的数)
思路:
1、如果用普通伸展树,那么会受到大小不是有序的问题的影响,1、2、4、5都很难完成;
2、于是,我们用一种更加高级的算法“树套树”来解决。这题我们用线段树套伸展树,其中线段树的作用是掌控伸展树,伸展树都是具有“左<根<右”的树,用于具体的查询。用通俗点的话来讲,这题中的每段线段树都吊着一颗它管辖范围内的伸展树;
3、如何搭建起一棵线段树+__棵伸展树?
(1)先把线段树的框架搭好:对于每棵线段树而言lc=now*2,rc=now*2+1,管理范围(父亲的 l ,mid)或(mid+1,父亲的 r );
(2)每次新加入1个值,就往线段树里对应的位置塞,当然每段线段树它记录的是一棵伸展树的根,所以新加的值要加入(Splay_ins)这棵伸展树中去;
(3)这样,一棵+__棵的大树,就长好了。现在区间为(l,r)的线段树中,就有一棵有大小之分的专管l-r的伸展树了。
4、有了这样庞大的数据结构,我们就可以开始装逼了:
1.在包含k且不重叠的伸展树中查找统计小于k的数的个数,+1就是k的排名;
2.这是本题的难点。通过二分 0~ma 的排名,尝试是否合理;
3.凡是包含pos的伸展树全部删去原来的值,在插入一个值为k的值;
4.在包含k且不重叠的伸展树中重复查找前驱,取最大的前驱为k的前驱;
5.在包含k且不重叠的伸展树中重复查找后继,取最小的后继为k的后继。
总结:如果一堆没有大小之分的数据偏要进行数据大小的处理,那么线段树套伸展树乃是首选。有了这样的基础后,伸展树的插入、查找和删除操作都建立在线段树的特定区间上,也就是说,线段树管理范围,伸展树才是真正的主人,发挥着查询、更改的重要作用。
代码:(以Splay开头的是伸展树的操作,以SegTree开头的是线段树的操作)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int ans;
int a[50010],root[4000005];//root[i]线段树编号为i的,所管理的伸展树的根
struct node
{
int f,d,c,son[2],n;
}tr[4000005];int len=0;
void Splay_add(int d,int f)
{
len++;
tr[len].d=d;tr[len].f=f;tr[len].c=tr[len].n=1;
tr[len].son[0]=tr[len].son[1]=0;
if(d<tr[f].d) tr[f].son[0]=len;
else tr[f].son[1]=len;
}
int Splay_findip(int now,int d)
{
int x=root[now];
while(tr[x].d!=d)
{
if(d<tr[x].d)
{
if(tr[x].son[0]==0) break;
x=tr[x].son[0];
}
else
{
if(tr[x].son[1]==0) break;
x=tr[x].son[1];
}
}
return x;
}
void Splay_update(int x)
{
tr[x].c=tr[x].n+tr[tr[x].son[0]].c+tr[tr[x].son[1]].c;
}
void Splay_rotate(int x,int w)
{
int f=tr[x].f;
int ff=tr[f].f;
int r,R;
R=f;r=tr[x].son[w];
tr[R].son[1-w]=r;
if(r!=0) tr[r].f=R;
R=ff;r=x;
if(tr[ff].son[0]==f) tr[R].son[0]=r;
else tr[R].son[1]=r;
tr[r].f=R;
R=x;r=f;
tr[R].son[w]=r;
tr[r].f=R;
Splay_update(f);
Splay_update(x);
}
void Splay_splay(int now,int x,int rt)
{
while(tr[x].f!=rt)
{
int f=tr[x].f;
int ff=tr[f].f;
if(rt==ff)
{
if(tr[f].son[0]==x) Splay_rotate(x,1);
else Splay_rotate(x,0);
}
else
{
if(tr[ff].son[0]==f&&tr[f].son[0]==x){Splay_rotate(f,1);Splay_rotate(x,1);}
else if(tr[ff].son[1]==f&&tr[f].son[1]==x){Splay_rotate(f,0);Splay_rotate(x,0);}
else if(tr[ff].son[0]==f&&tr[f].son[1]==x){Splay_rotate(x,0);Splay_rotate(x,1);}
else if(tr[ff].son[1]==f&&tr[f].son[0]==x){Splay_rotate(x,1);Splay_rotate(x,0);}
}
}
if(rt==0) root[now]=x;
}
void Splay_ins(int now,int d)//在编号为now的伸展树下,插入值d
{
if(root[now]==0){Splay_add(d,0);root[now]=len;return ;}
int x=Splay_findip(now,d);
if(tr[x].d==d)
{
tr[x].n++;
Splay_update(x);
Splay_splay(now,x,0);
}
else
{
Splay_add(d,x);
Splay_update(x);
Splay_splay(now,len,0);
}
}
int Splay_findrank(int now,int d)//在编号为now的伸展树下,查询小于值d的数的个数
{
int x=Splay_findip(now,d);
Splay_splay(now,x,0);
if(tr[x].d<d) return tr[tr[x].son[0]].c+tr[x].n;
else return tr[tr[x].son[0]].c;
}
void Splay_del(int now,int d)
{
int x=Splay_findip(now,d);Splay_splay(now,x,0);
if(tr[x].n>1){tr[x].n--;Splay_update(x);}
else if(tr[x].son[0]==0&&tr[x].son[1]==0){root[now]=0;}
else if(tr[x].son[0]==0&&tr[x].son[1]!=0){root[now]=tr[x].son[1];tr[root[now]].f=0;}
else if(tr[x].son[0]!=0&&tr[x].son[1]==0){root[now]=tr[x].son[0];tr[root[now]].f=0;}
else
{
int p=tr[x].son[0];
while(tr[p].son[1]!=0) p=tr[p].son[1];
Splay_splay(now,p,x);
int R=p,r=tr[x].son[1];
tr[R].son[1]=r;
tr[r].f=R;
root[now]=R;tr[root[now]].f=0;
Splay_update(R);
}
}
int Splay_findqianqu(int now,int d)//x<d
{
int x=Splay_findip(now,d);Splay_splay(now,x,0);
if(tr[x].d>=d&&tr[x].son[0]!=0)
{
x=tr[x].son[0];
while(tr[x].son[1]!=0) x=tr[x].son[1];
}
else if(tr[x].d>=d) return -999999999;
return tr[x].d;
}
int Splay_findhouji(int now,int d)//x>d
{
int x=Splay_findip(now,d);Splay_splay(now,x,0);
if(tr[x].d<=d&&tr[x].son[1]!=0)
{
x=tr[x].son[1];
while(tr[x].son[0]!=0) x=tr[x].son[0];
}
else if(tr[x].d<=d) return 999999999;
return tr[x].d;
}
void SegTree_ins(int now,int l,int r,int x,int d)//在区间内插入值d
{
int mid=(l+r)/2;
Splay_ins(now,d);//该区间的伸展树插入值d
if(l==r) return ;
else if(x<=mid) SegTree_ins(now<<1,l,mid,x,d);
else SegTree_ins(now<<1|1,mid+1,r,x,d);
}
void SegTree_askrank(int now,int l,int r,int al,int ar,int d)//查询区间(al,ar)中小于值d的数的个数
{
int mid=(l+r)/2;
if(al<=l&&r<=ar)//若该线段树(l,r)在查询范围内
{
ans+=Splay_findrank(now,d);
return ;
}
if(al<=mid) SegTree_askrank(now<<1,l,mid,al,ar,d);
if(mid+1<=ar) SegTree_askrank(now<<1|1,mid+1,r,al,ar,d);
}
void SegTree_change(int now,int l,int r,int pos,int d)
{
int mid=(l+r)/2;
Splay_del(now,a[pos]);
Splay_ins(now,d);
if(l==r) return ;
if(pos<=mid) SegTree_change(now<<1,l,mid,pos,d);
else SegTree_change(now<<1|1,mid+1,r,pos,d);
}
void SegTree_askqianqu(int now,int l,int r,int al,int ar,int d)
{
int mid=(l+r)/2;
if(al<=l&&r<=ar)
{
ans=max(ans,Splay_findqianqu(now,d));
return ;
}
if(al<=mid) SegTree_askqianqu(now<<1,l,mid,al,ar,d);
if(mid+1<=ar) SegTree_askqianqu(now<<1|1,mid+1,r,al,ar,d);
}
void SegTree_askhouji(int now,int l,int r,int al,int ar,int d)
{
int mid=(l+r)/2;
if(al<=l&&r<=ar)
{
ans=min(ans,Splay_findhouji(now,d));
return ;
}
if(al<=mid) SegTree_askhouji(now<<1,l,mid,al,ar,d);
if(mid+1<=ar) SegTree_askhouji(now<<1|1,mid+1,r,al,ar,d);
}
int main()
{
int n,m,ma=-999999999;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
ma=max(ma,a[i]);
SegTree_ins(1,1,n,i,a[i]);
}
while(m--)
{
int opt,l,r,pos,k;
scanf("%d",&opt);
switch(opt)
{
case 1:
{
scanf("%d%d%d",&l,&r,&k);
ans=0;
SegTree_askrank(1,1,n,l,r,k);
printf("%d\n",ans+1);
break;
}
case 2:
{
scanf("%d%d%d",&l,&r,&k);
int head=0,tail=ma,Ans;
while(head<=tail)
{
int mid=(head+tail)/2;
ans=1;
SegTree_askrank(1,1,n,l,r,mid);
if(ans<=k)
{
Ans=head;
head=mid+1;
}
else tail=mid-1;
}
printf("%d\n",Ans);
break;
}
case 3:
{
scanf("%d%d",&pos,&k);
SegTree_change(1,1,n,pos,k);
a[pos]=k;
ma=max(ma,k);
break;
}
case 4:
{
scanf("%d%d%d",&l,&r,&k);
ans=0;
SegTree_askqianqu(1,1,n,l,r,k);
printf("%d\n",ans);
break;
}
case 5:
{
scanf("%d%d%d",&l,&r,&k);
ans=999999999;
SegTree_askhouji(1,1,n,l,r,k);
printf("%d\n",ans);
break;
}
}
}
}
做完这两道伸展树的题后,我不禁感叹:伸展树真的太神奇了!
推荐:《伸展树—作用介绍》http://blog.csdn.net/a_bright_ch/article/details/72795172