大二上的时候。写过一个AVL的操作演示,今天一看Splay。发现和AVL事实上一样,加上线段树的基础,懒惰标记什么都知道。学起来轻松很多哦

我參考的模板来自这里  http://blog.csdn.net/u013480600/article/list/2

里面有大量的ch[r][0] ch[r][1]等 我建议用宏定义代替,写的时候方括号少打了非常多,等做的题多得时候,我再把自己使用的模板发来


#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

/*===============================
Splay树模板
1、tot1,tot2都是从1開始
2、
================================*/

#define key_value ch[ch[root][1]][0]
#define ls(r) ch[r][0]
#define rs(r) ch[r][1]
#define ll long long
#define repe(i,s,e) for(int i=s;i<=e;i++)
#define rep(i,s,e) for(int i=s;i<e;i++)

const int MAXN=100000 +10;
int pre[MAXN],ch[MAXN][2],sz[MAXN],root,tot1;
//  父节点、左右孩子(0为左,同一时候0为左旋)、子树规模、根节点、结点数量
int key[MAXN];//结点的值
int add[MAXN];
ll sum[MAXN];//sum[i]=v 以i为root的树的和,
int s[MAXN],tot2;/////???

?

?

?

//内存池、内存池容量(这题用不到。假设有删除操作。内存不够能够这样 //s中存的是被回收的内存,从后面能够看到,tot2不为0的时候优先使用s[tot2]=x中的 //pre[x],ch[x],size[x]等, //否则就使用tot1++之后产生的数x的相应位置 int a[MAXN];//初始时的数组,建树时用 int n,q; void newnode(int &r,int father, int k)//r必须是& { if(tot2)r=s[tot2--];//取得时候tot2--,存++tot2 else r=++tot1; pre[r]=father; sz[r]=1; key[r]=k; add[r]=sum[r]=0; ch[r][0]=ch[r][1]=0; } void updateadd(int r, int av) { if(!r)return;//?

? add[r]+=av; key[r]+=av; sum[r]+=(ll)av*sz[r]; } //通过孩子结点更新父亲结点 void pushup(int r) { sz[r]=sz[ls(r)]+sz[rs(r)]+1; sum[r]=sum[ls(r)]+sum[rs(r)]+key[r]; } //将延迟标记更新到孩子结点 void pushdown(int r) { if(add[r]) { updateadd(ls(r),add[r]); updateadd(rs(r),add[r]); add[r]=0; } } //建树区间[l,r]。先建立中间结点,再建两端。 //注意和线段树的差别 void build(int &x, int l, int r, int father) { if(l>r)return; int mid=(l+r)/2; newnode(x, father, a[mid]); build(ch[x][0], l, mid-1, x); build(ch[x][1], mid+1, r, x); pushup(x); } //初始化。前后各加一个king结点 void Init() { for(int i=1;i<=n;i++) scanf("%d",&a[i]); root=tot1=tot2=0; ls(root)=rs(root)=pre[root]=sz[root]=add[root]=sum[root]=key[root]=0; newnode(root, 0, -1);//root'sfatheris -1 newnode(rs(root),root,-1);//头尾各插入一个空 build(key_value, 1, n, rs(root)); pushup(rs(root)); pushup(root); } //旋转,0为左旋。1为右旋 该部分基本固定 void rota(int x, int kind) { int y=pre[x]; pushdown(y); pushdown(x);//必须先把y的标记向下传递。在传x ch[y][!kind]=ch[x][kind],pre[ch[x][kind]]=y; if(pre[y])//??y的父节点不是root ch[pre[y]][ rs(pre[y])==y ]=x;//仅仅能对这句牛逼的代码说声我屮艸芔茻 pre[x]=pre[y]; ch[x][kind]=y,pre[y]=x; pushup(y);//维护y结点 x节点的信息不用PushUp吗?

能够证明这里除了x节点维护的信息不正确外,其它全部点信息都正确 //Push_Up(x);//这个能够不写,详见Crash:运用伸展树解决数列维护问题 论文,可是写了也不会多多少时间消耗 } void Splay(int r, int goal)//将r调整到goal以下 { pushdown(r);// 离开之前把懒惰标记的信息传递 while(pre[r]!=goal) { if(pre[pre[r]]==goal) rota(r, ch[pre[r]][0]==r);//r在左子树。右旋 else { int y=pre[r]; int kind=ch[pre[y]][0]==y; if(ch[y][kind]==r)//之字形旋转 {//y在其父节点的左子树上,r在y的右子树上 //或者y在其父节点的右子树。r在y左子树 rota(r, !kind); rota(r, kind); } else //一字型旋转 { rota(y, kind);//注意这里是y啊 rota(r, kind); } } } pushup(r); if(goal==0)root=r; } //得到第k个结点编号 int getkth(int r, int k) { pushdown(r); int t=sz[ls(r)]+1; if(t==k)return r; if(t>k)return getkth(ls(r),k);//在左子树第k个 else return getkth(rs(r), k-t);//在右子树第k-t个 } //得到以r为根的第一个结点--最左边的节点编号 int getmin(int r) { pushdown(r); while(ls(r)) { r=ls(r); pushdown(r); } return r; } //得到以r为根的最后一个结点--最右边的编号 int getmax(int r) { pushdown(r); while(rs(r)) { r=rs(r); pushdown(r); } return r; } void addv(int l, int r, int d) { Splay(getkth(root,l),0); Splay(getkth(root,r+2),root); updateadd(key_value, d); pushup(rs(root)); pushup(root); } ll Querysum(int l, int r) { Splay(getkth(root, l),0);//第l个点到根结点 Splay(getkth(root, r+2), root);//第r+2个点到根结点的右孩子 return sum[key_value]; } int main() { //freopen("poj3468.txt","r",stdin); int q; while(~scanf("%d%d", &n, &q)) { Init(); while(q--) { char op[30]; int x,y,z; scanf("%s",op); if(op[0]=='Q') { scanf("%d%d",&x,&y); printf("%lld\n",Querysum(x,y)); } else { scanf("%d%d%d",&x,&y,&z); addv(x,y,z); } } } return 0; }



posted on 2017-05-10 13:32  lxjshuju  阅读(112)  评论(0编辑  收藏  举报