【模板Splay】XX树
来源:互联网 发布:java 接口中静态方法 编辑:程序博客网 时间:2024/06/06 00:10
话说 大概半年前专门去学了下Splay,在bzoj上写了几道题
但是,当时觉得自己很菜,想多学点东西,再一次性写出来,然后。。。。
我现在都忘掉了自己还会这个 mmp。
要讲Splay,那就必然需要了解一下旋转操作。
http://www.cnblogs.com/kuangbin/archive/2012/10/07/2714068.html
先去研究一下上述博客知识,学一下旋转操作的原理! 很重要
然后我找到了黄学长的Splay 模板,全数组构成,哇,真是好开心啊,收藏收藏:
http://hzwer.com/2841.html
以下的代码 基本都是按照kuangbin 的代码风格来写的
我的理解:
我们构造一颗二叉排序树,然后我们所有的一切操作:插入、删除、反转、旋转 都是建立在 维护这棵二叉排序树上的。但是我们要搞清楚的一点是:这棵排序树并不是以大小来排序的,而是以 我们要处理的数组 的下标来排序。 所以我们一直都在维护这个数组的相对顺序,而不是大小。
旋转 Rotate :有三种情况需要讨论 : (PS:kuangbin 博客的 单旋图貌似是有问题的,很明显的相对顺序都没有维护)
1. 节点x的父节点y是根节点。
2 Zig-Zig或Zag-Zag操作
节点x的父节点y不是根节点,且x与y同时是各自父节点的左孩子或者同时是各自父节点的右孩子。
3.Zig-Zag或Zag-Zig操作:节点x的父节点y不是根节点,x与y中一个是其父节点的左孩子而另一个是其父节点的右孩子。
我们可以看到这三种情况的旋转我们都将X旋转至根节点。 并且我们同时维护了他们的相对顺序大小。
我们如何实现上述的操作?:
通过入门题来讲解讲解:
POJ 3468 成段更新+区间求和
* 题目给定了n个数A1,A2,…An,有以下两种操作
* C a b c:把c加入到Aa,Aa+1,..Ab中
* Q a b:查询Aa,Aa+1,..Ab的和
开的空间:
#define key_value ch[ch[root][1]][0] //经常利用这个#define N 200005const int INF=0x3f3f3f3f;int pre[N],ch[N][2],key[N],size[N],add[N],rev[N],m[N];/*pre:父节点 ch[N][0][1]左节点和右节点。 size子树规模,root树的当前的根 , key:这个节点代表的值 add增量延迟标记 m记录子树和。 */int root,tot;int n;int a[N];void newnode(int &r,int father,int k){ //注意r 需要引用 r=++tot; //可知我们的根节点从1开始 pre[r]=father; //更新 pre size[r]=1; //新开节点只有自己 add[r]=rev[r]=0; // 标记初识为0 ch[r][0]=ch[r][1]=0; key[r]=m[r]=k; }void push_rev(int r){ //反转操作 if(r){ swap(ch[r][0],ch[r][1]); //把当前r的左右子树反转,并且把反转传递下去。 rev[r]^=1; }}void up_add(int r,int d){ // 给根为r的子树增加值,一定要先把当前节点的标记更新掉,然后再加上延迟标记! if(r==0) return; add[r]+=d; key[r]+=d; m[r]+=d;}void push_down(int r){ //将lazy 标记更新到孩子节点 if(rev[r]){ push_rev(ch[r][0]); push_rev(ch[r][1]); rev[r]=0; } if(add[r]){ up_add(ch[r][0],add[r]); up_add(ch[r][1],add[r]); add[r]=0; }}void push_up(int r){ //向上更新 size[r]=size[ch[r][0]] + size[ch[r][1]] +1; m[r]=key[r]; if(ch[r][0]) m[r]=min(m[ch[r][0]] , m[r]); // 因为我们的删除就是归零,所以要判断是否存在 if(ch[r][1]) m[r]=min(m[ch[r][1]] , m[r]);}void build(int &x,int l,int r,int father){ ////先建立中间结点,再两端的方法 if(l>r) return; int mid=(l+r)>>1; newnode(x,father,a[mid]); //key=a[mid], 我们按照数组的下标来建树 build(ch[x][0],l,mid-1,x); // x =father build(ch[x][1],mid+1,r,x); push_up(x);}void init(){ //kuangbin一惯的风格init() tot=root=0; ch[root][0]=ch[root][1]=pre[root]=size[root]=rev[root]=0; //root 地址为0,所以不需要赋m[root]的值 /*开一个理论上永远最大的根,和永远最小的根,这样操作有套路*/ newnode(root,0,INF); // 这个点是永远最小的 newnode(ch[root][1],root,INF); //这个点永远最大。 build(key_value,1,n,ch[root][1]); //建树 push_up(ch[root][1]); //记得建完树之后更新到root push_up(root);}void Rotate(int x,int kind)//对X旋转,0为左旋,1为右旋 该部分基本固定{ int y=pre[x]; push_down(y); //正确的顺序先传递y,然后再传递x 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个节点的位置。但是我们注意init的时候添加两个极大极小点。{ 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){ Splay(get_kth(root,l),0); //l-1 Splay(get_kth(root,r+2),root); //r+1 up_add(key_value,D); push_up(ch[root][1]); push_up(root);}void REVERSE(int l,int r){ Splay(get_kth(root,l),0); //将第l-1个节点调制0 Splay(get_kth(root,r+2),root); //将r+1旋转至新的root,那么key_value就是(l-1,r+2) push_rev(key_value); push_up(ch[root][1]); push_up(root);}void REVOLVE(int l,int r,int T) //循环右移{ int len=r-l+1; // len:长度 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); //将root 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); key_value=tmp; pre[key_value]=ch[root][1];//这个不用忘记 push_up(ch[root][1]); push_up(root);}void insert(int x,int p){ Splay(get_kth(root,x+1),0); // x转至根 Splay(get_kth(root,x+2),root); //x+1转到root右儿子,此时x+2.... 都在右边 newnode(key_value,ch[root][1],p); push_up(ch[root][1]); push_up(root);}void del(int x){ Splay(get_kth(root,x),0); //x-1 移到0 Splay(get_kth(root,x+2),root); //x+1 移到x-1的右边 那么key_value=x pre[key_value]=0; key_value=0; //不知道算不算清除 // push_up(ch[root][1]); push_up(root);}int qur_min(int l,int r){ Splay(get_kth(root,l),0); Splay(get_kth(root,r+2),root); return m[key_value];}int main() { // freopen("1.txt","r",stdin); while (~scanf("%d",&n)) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); init();//这个不能忘记 int q; scanf("%d",&q); char op[10]; while(q--){ scanf("%s",op); if(op[0]=='A'){ int l,r,ad; scanf("%d %d %d",&l,&r,&ad); ADD(l,r,ad); } else if(op[0]=='I'){ int x,p; scanf("%d %d",&x,&p); insert(x,p); } else if(op[0]=='D'){ int x; scanf("%d",&x); del(x); } else if(op[0]=='M'){ int l,r; scanf("%d %d",&l,&r); printf("%d\n",qur_min(l,r)); } else if(op[3]=='E'){ //REVERSE 反转 int l,r; scanf("%d %d",&l,&r); REVERSE(l,r); } else{ // REVOLVE int l,r,k; scanf("%d %d %d",&l,&r,&k); REVOLVE(l,r,k); } } } return 0;}
“`
- 【模板Splay】XX树
- Splay伸展树&模板
- 伸展树(splay)模板
- Splay树模板
- splay树模板
- Splay伸展树模板总结
- 【模板】Splay二叉树排序
- SPLAY模板
- (模板)splay
- splay模板
- splay 模板
- splay 模板
- 【模板】splay
- splay模板
- splay模板
- Splay模板
- Splay模板
- splay 模板
- Eclipse编辑Spring配置文件xml时自动提示类class包名
- SIFT特征提取分析
- C语言模块化开发简单实例——实现一个计算器的加减乘除
- C++输出彩色字到控制台
- 算法-最大子序列和
- 【模板Splay】XX树
- 移动端实现下拉刷新
- 一键找出所有的依赖并且打包
- MySQL如何创建数据库并查询数据类型
- 文件IO编程一
- C++中WSAAsyncSelect模型的用法例程
- 反汇编看c++引用
- 相关文章、关联文章、产品功能开发方案
- Asp.net技术整理