Splay树可以利用 我Treap树的程序中的rotate函数实现 Splay(x,S)功能,将节点x伸展的节点S处。Splay(x,S)时有三种情况:
1、zig情况。
X是查找路径上我们需要旋转的一个非根节点。
如果X的父节点是根,那么我们用下图所示的方法旋转X到根:
图2
这和一个普通的单旋转相同。
2、zig-zag情况。
在这种情况中,X有一个父节点P和祖父节点G(P的父节点)。X是右子节点,P是左子节点,或者反过来。这个就是双旋转。
先是X绕P左旋转,再接着X绕G右旋转。
如图所示:
图三
3、zig-zig情况。
这和前一个旋转不同。在这种情况中,X和P都是左子节点或右子节点。
先是P绕G右旋转,接着X绕P右旋转。
如图所示:
图四
我就是zig-zag搞糊了WA了一下午。。一天比一天苦
下面附个标程:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<algorithm> 5 using namespace std; 6 struct Tree_Node{ 7 int ch[2],par,x,sum; 8 bool rev_flag; 9 }Tree[100100]; 10 int Tree_Cnt; 11 int n,m; 12 13 void Tree_Update(int v){ 14 Tree[v].sum=1; 15 if(Tree[v].ch[0]) Tree[v].sum+=Tree[Tree[v].ch[0]].sum; 16 if(Tree[v].ch[1]) Tree[v].sum+=Tree[Tree[v].ch[1]].sum; 17 } 18 void Tree_Pushdown(int v){ 19 Tree[v].rev_flag=false; 20 int t=Tree[v].ch[0]; 21 Tree[v].ch[0]=Tree[v].ch[1]; 22 Tree[v].ch[1]=t; 23 if(Tree[v].ch[0]) Tree[Tree[v].ch[0]].rev_flag=(!Tree[Tree[v].ch[0]].rev_flag); 24 if(Tree[v].ch[1]) Tree[Tree[v].ch[1]].rev_flag=(!Tree[Tree[v].ch[1]].rev_flag); 25 } 26 27 void Tree_Rotate(int v,int k){ 28 int X=Tree[v].ch[k]; 29 30 int f=(Tree[Tree[v].par].ch[1]==v); 31 Tree[Tree[v].par].ch[f]=X; 32 Tree[X].par=Tree[v].par; 33 Tree[v].par=X; 34 Tree[v].ch[k]=Tree[X].ch[!k]; 35 if(Tree[X].ch[!k]) Tree[Tree[X].ch[!k]].par=v; 36 Tree[X].ch[!k]=v; 37 38 Tree_Update(v);Tree_Update(X); 39 } 40 void Tree_Splay(int v,int to){ 41 if(v==to) return; 42 int p1=Tree[v].par; 43 int f1=(Tree[p1].ch[1]==v); 44 if(p1==to){ 45 Tree_Rotate(p1,f1); 46 return; 47 } 48 int p2=Tree[p1].par; 49 int f2=(Tree[p2].ch[1]==p1); 50 if(f1==f2){ 51 Tree_Rotate(p2,f1); 52 Tree_Rotate(p1,f1); 53 } 54 else{ 55 Tree_Rotate(p1,f1); 56 Tree_Rotate(p2,f2); 57 } 58 if(p2==to) return; 59 Tree_Splay(v,to); 60 } 61 void Tree_Ins(int par,int &v,int x){ 62 if(v==0){ 63 Tree[v=++Tree_Cnt].x=x; 64 Tree[v].par=par; 65 Tree[v].sum=1; 66 Tree_Splay(v,Tree[0].ch[0]); 67 } 68 else{ 69 Tree[v].sum++; 70 int f=(x>Tree[v].x); 71 Tree_Ins(v,Tree[v].ch[f],x); 72 } 73 } 74 int Tree_Find(int v,int x){ 75 if(Tree[v].rev_flag) Tree_Pushdown(v); 76 int mid=1; 77 if(Tree[v].ch[0]) mid+=Tree[Tree[v].ch[0]].sum; 78 if(x==mid){Tree_Splay(v,Tree[0].ch[0]);return v;} 79 if(x<mid) return Tree_Find(Tree[v].ch[0],x); 80 if(x>mid) return Tree_Find(Tree[v].ch[1],x-mid); 81 } 82 83 void Tree_Rev(int x,int y){ 84 x++,y++; 85 int v1=Tree_Find(Tree[0].ch[0],x-1); 86 int v2=Tree_Find(Tree[0].ch[0],y+1); 87 88 Tree_Splay(v1,Tree[0].ch[0]); 89 Tree_Splay(v2,Tree[Tree[0].ch[0]].ch[1]); 90 int t=Tree[Tree[Tree[0].ch[0]].ch[1]].ch[0]; 91 Tree[t].rev_flag=(!Tree[t].rev_flag); 92 } 93 void Print_Out(int v){ 94 if(Tree[v].rev_flag) Tree_Pushdown(v); 95 if(Tree[v].ch[0]) Print_Out(Tree[v].ch[0]); 96 if(Tree[v].x>=1 && Tree[v].x<=n) printf("%d ",Tree[v].x); 97 if(Tree[v].ch[1]) Print_Out(Tree[v].ch[1]); 98 } 99 100 int main(){ 101 scanf("%d%d",&n,&m); 102 for(int i=1;i<=n;i++) Tree_Ins(0,Tree[0].ch[0],i); 103 Tree_Ins(0,Tree[0].ch[0],0);Tree_Ins(0,Tree[0].ch[0],n+1); 104 105 int x,y; 106 for(int i=0;i<m;i++){ 107 scanf("%d%d",&x,&y); 108 Tree_Rev(x,y); 109 } 110 Print_Out(Tree[0].ch[0]); 111 return 0; 112 }
PS:程序中用Tree[0].ch[0]表示根节点,这样便于操作。每次插入和查找等操作都要Splay一次,这样才能维护Splay树的均摊时间复杂度,不至于超时,要翻转一个区间时只需把这个区间之前的一个元素Splay到根节点,之后的一个元素Splay到根节点的右儿子,则根节点的右儿子的左二子即为所求区间,打上翻转标记即可。此题n<=10 0000,当n=10 0000时Tree_Ins函数的递归调用在windows下会爆栈,但好在评测机是Linux,栈大点。。于是没爆。Linux的栈大小一般为8M。