学了splay 后 撸的第一道题。
是看了大神的教学,引用了大神的核心代码才会的:splay tree 学习
刚开始没注意:使用一个节点你需要把他的标记先下压。提供了好多次wa。
/*题意:给一个数组,有6种操作: (1)add x y d : [x,y]中所有数加d ----x-1 ---------y+1 ---[x,y] 给y-1的左子树加上+d 标记 (2)reverse x y: [x,y]区间翻转 ---- x-1 -------------y+1 -------[x,y] 给y-1的左子树加上翻转标记(即左右子树概念上要互换,去标记就执行这步操作) (3)revolve x y t: [x,y]循环右移t位,t可取任意整数 ------x-1 -------------------y+1 -------------y-t -----[x,y-t-1]--[y-t+1,y] 上两层操作同上,y-t splay到 y-1的左子树处 -----x-1 ---------------y+1 ------------y-t -----[x,y-t-1] p2=[y-t+1,y] 断开y-t 右子树,将x splay到y-t的位置 -----x-1 ---------------y+1 ------------x - p2=[y-t+1,y]-----[x+1,y-t-1] 中序遍历为循环移位顺序 (4)insert x v: 在第x个数后面插入P -----x -------x+1 可以知道 x-1左子树为Null插入p -----x --------x+1 -----p (5)delete x : 删除第x个数 -----x-1 ---------x+1 ------x 删除x (6)min x y : 求区间[x,y]的最小值 ----x-1 --------y+1 ----[x,y] 结尾都把更改的节点翻转到树根 求值即可 */ #include <stdio.h> #include <string.h> #include <stdlib.h> #define Max_N 100008 #define INF 0x3f3f3f3f #define ZIG 1 #define ZAG 0 #define find_min(a,b) a>b?b:a using namespace std; struct sp_node{ // pre 表示指向父亲的指针 //ch[0],ch[1]分别是左儿子和右儿子 sp_node *pre,*ch[2]; int val,lazy,Min,size; //lazy add()的延迟标记 size表示节点的子树有多少节点 bool isRev;//翻转的延迟标记 }; class sp_tree { public: void init(int *input,int n);//根据输入序列进行初始化 void add(int x,int y,int d);//[x,y]中所有数加d void reverse(int x,int y);//[x,y]区间翻转 void revolve(int x,int y,int t);//[x,y]循环右移t位,t可取任意整数 void insert(int x,int v);//在第x个数后面插入P void Delete(int x);//删除第x个数 int get_min(int x,int y);//求区间[x,y]的最小值 private: sp_node nstack[Max_N*4]; int scnt; sp_node *root; sp_node *get_sp_node();//从栈里得到到一个节点; sp_node *find_sp_node(int x);//寻找编号为x的节点 sp_node *create_sp_tree(int *input,int l,int r);//递归创造一个平衡二叉排序树 void rotate(sp_node *x,int c);//旋转操作,c=0 表示左旋,c=1 表示右旋 void splay(sp_node *,sp_node *f);// Splay 操作,表示把结点x 转到结点f 的下面 void push_down(sp_node *x);//把x标记下传 void update(sp_node *x);//更新维护 x节点 void select(int k,sp_node *f);// 找到处在中序遍历第k 个结点,并将其旋转到结点f 的下面 void infix_print(sp_node *root)//输出查看 { if(!root) return; infix_print(root->ch[0]); //printf("val:%d size:%d min:%d lazy:%d isRev:%d\n", // root->val,root->size,root->Min,root->lazy,root->isRev); printf("%d ",root->val); infix_print(root->ch[1]); } }; sp_node *sp_tree::get_sp_node() { sp_node *sp=nstack+scnt; sp->ch[0]=sp->ch[1]=sp->pre=NULL; sp->Min=INF,sp->isRev=0,sp->lazy=0,sp->size=0; ++scnt; return sp; } sp_node *sp_tree::create_sp_tree(int *input,int l,int r) { if(l>r) return NULL; int mid=(l+r)/2; sp_node *spn=get_sp_node(); spn->ch[0]=create_sp_tree(input,l,mid-1); spn->ch[1]=create_sp_tree(input,mid+1,r); spn->val=input[mid],spn->size=r-l+1,spn->Min=input[mid]; if(spn->ch[0]) { spn->Min=find_min(spn->Min,spn->ch[0]->Min); spn->ch[0]->pre=spn; } if(spn->ch[1]) { spn->Min=find_min(spn->Min,spn->ch[1]->Min); spn->ch[1]->pre=spn; } return spn; } /*大神模板开始*/ void sp_tree::rotate(sp_node *x,int c) {// 旋转操作,c=0 表示左旋,c=1 表示右旋 sp_node *y=x->pre; push_down(y),push_down(x); //先将Y 结点的标记向下传递(因为Y 在上面),再把X 的标记向下传递 y->ch[!c]=x->ch[c]; if(x->ch[c]!=NULL) x->ch[c]->pre=y; x->pre=y->pre; if(y->pre!=NULL) if(y->pre->ch[0]==y) y->pre->ch[0]=x; else y->pre->ch[1]=x; x->ch[c]=y,y->pre=x,update(y);// 维护Y 结点 if(y==root) root=x; //root 表示整棵树的根结点 } void sp_tree::splay(sp_node *x,sp_node *f) {//特别地,转到根就是转到空结点Null 的下面 for(push_down(x);x->pre!=f;) // 一开始就将X 的标记下传 if(x->pre->pre==f) // 父结点的父亲即为f,执行单旋转 if(x->pre->ch[0]==x) rotate(x,ZIG); else rotate(x,ZAG); else { sp_node *y=x->pre,*z=y->pre; if(z->ch[0]==y) if(y->ch[0]==x) rotate(y,ZIG),rotate(x,ZIG); // 一字形旋转 else rotate(x,ZAG),rotate(x,ZIG); // 之字形旋转 else if(y->ch[1]==x) rotate(y,ZAG),rotate(x,ZAG); // 一字形旋转 else rotate(x,ZIG),rotate(x,ZAG); // 之字形旋转 } update(x);// 最后再维护X 结点 } void sp_tree::select(int k,sp_node *f) { int tmp; sp_node *t; for(t=root;;) { //因为每次都要标记下压,所以不用担心翻转问题 push_down(t); // 由于要访问t 的子结点,将标记下传 if(t->ch[0]) tmp=t->ch[0]->size; // 得到t 左子树的大小 else tmp=0; if(k==tmp) break; if(k<tmp)// 第k 个结点在t 左边,向左走 t=t->ch[0]; // 否则在右边,而且在右子树中,这个结点不再是第k 个 else k-=tmp+1,t=t->ch[1]; } splay(t,f); } /*大神模板结束*/ /*看大神的模板,下压标记的操作和更新节点很经典,就是每次进行操作前都把标记下压(当前节点把延迟的 操作给解决掉,延迟标记传递给儿子),这样就不用考虑当前结点的标记问题. 也就是说参与splay的节点都解决了延迟标记和更新了信息 */ void sp_tree::push_down(sp_node *x) {/*push_down主要是应用区间树的延迟标记技术中。 x拥有的标记表示以x为根的树有 标记的操作,但没有做 让x执行标记的操作和下压标记 */ if(!x) return; if(x->lazy) { int w=x->lazy; x->val+=w; if(x->ch[0]) x->ch[0]->lazy+=w,x->ch[0]->Min+=w; if(x->ch[1]) x->ch[1]->lazy+=w,x->ch[1]->Min+=w; x->lazy=0; } if(x->isRev) { sp_node *t=x->ch[0]; x->ch[0]=x->ch[1],x->ch[1]=t,x->isRev=0; if(x->ch[0]) x->ch[0]->isRev^=1; if(x->ch[1]) x->ch[1]->isRev^=1; } } void sp_tree::update(sp_node *x) { if(!x) return; x->size=1,x->Min=x->val; if(x->ch[0]) { x->Min=find_min(x->Min,x->ch[0]->Min); x->size+=x->ch[0]->size; } if(x->ch[1]) { x->Min=find_min(x->Min,x->ch[1]->Min); x->size+=x->ch[1]->size; } } void sp_tree::init(int *input,int n) { input[0]=input[n+1]=INF; scnt=0; root=create_sp_tree(input,0,n+1); } void sp_tree::add(int x,int y,int d) { /* (1)add x y d : [x,y]中所有数加d ----x-1 ---------y+1 ---[x,y] 给y-1的左子树加上+d 标记 */ sp_node *sp; select(x-1,NULL),select(y+1,root); sp=root->ch[1],push_down(sp); sp=sp->ch[0],sp->lazy+=d,push_down(sp); splay(sp,NULL); } void sp_tree::reverse(int x,int y) { /* (2)reverse x y: [x,y]区间翻转 ---- x-1 -------------y+1 -------[x,y] 给y-1的左子树加上翻转标记*/ sp_node *sp; select(x-1,NULL),select(y+1,root); sp=root->ch[1],push_down(sp); sp=sp->ch[0],push_down(sp),sp->isRev^=1; splay(sp,NULL); } void sp_tree::revolve(int x,int y,int t) { /* [x,y]循环右移t位,t可取任意整数 ------x-1 -------------------y+1 -------------y-t -----[x,y-t-1]--[y-t+1,y] 上两层操作同上,y-t splay到 y-1的左子树处 -----x-1 ---------------y+1 ------------y-t -----[x,y-t-1] p2=[y-t+1,y] 断开y-t 右子树,将x splay到y-t的位置 -----x-1 ---------------y+1 ------------x - p2=[y-t+1,y]-----[x+1,y-t-1] 中序遍历为循环移位顺序 */ int tn=y-x+1; t=(t%tn+tn)%tn; if(x==y||t==0) return; sp_node *p1,*p2; select(x-1,NULL),select(y+1,root),select(y-t,root->ch[1]); p1=root->ch[1]->ch[0],push_down(p1); p2=p1->ch[1],p1->ch[1]=NULL; select(x,root->ch[1]); p1=root->ch[1]->ch[0],push_down(p1); p1->ch[0]=p2,p2->pre=p1; splay(p2,NULL); } void sp_tree::insert(int x,int v) { /* 在第x个数后面插入P -----x -------x+1 可以知道 x-1左子树为Null插入p -----x --------x+1 -----p */ sp_node *sp; select(x,NULL),select(x+1,root); sp=root->ch[1],push_down(sp); sp_node *p=get_sp_node(); p->val=v; sp->ch[0]=p,p->pre=sp; splay(p,NULL); } void sp_tree::Delete(int x) { /* delete x : 删除第x个数 -----x-1 ---------x+1 ------x 删除x */sp_node *sp; select(x-1,NULL),select(x+1,root); sp=root->ch[1],push_down(sp); sp->ch[0]=NULL; splay(sp,NULL); } int sp_tree::get_min(int x,int y) { /* min x y : 求区间[x,y]的最小值 ----x-1 --------y+1 ----[x,y] */sp_node *sp; select(x-1,NULL),select(y+1,root); //infix_print(root); sp=root->ch[1],push_down(sp); sp=sp->ch[0],push_down(sp); int res=sp->Min; splay(sp,NULL); return res; } int input[Max_N]; char choice[6][10]={"ADD","REVERSE","REVOLVE","INSERT","DELETE","MIN"}; sp_tree spt; int main() { int n; while(~scanf("%d",&n)) { for(int i=1;i<=n;++i) scanf("%d",input+i); spt.init(input,n); int m; scanf("%d",&m); char str[10]; int x,y,d; while(m--) { scanf("%s",str); if(!strcmp(choice[0],str)) { scanf("%d %d %d",&x,&y,&d); spt.add(x,y,d); } else if(!strcmp(choice[1],str)) { scanf("%d %d",&x,&y); spt.reverse(x,y); } else if(!strcmp(choice[2],str)) { scanf("%d %d %d",&x,&y,&d); spt.revolve(x,y,d); } else if(!strcmp(choice[3],str)) { scanf("%d %d",&x,&d); spt.insert(x,d); } else if(!strcmp(choice[4],str)) { scanf("%d %d",&x,&d); spt.Delete(x); } else if(!strcmp(choice[5],str)) { scanf("%d %d",&x,&y); printf("%d\n",spt.get_min(x,y)); } } } return 0; }