初学伸展树区间建树(A Simple Problem with Integers)

来源:互联网 发布:h5棋牌源码论坛 编辑:程序博客网 时间:2024/06/07 06:27

一.几个重要概念

1.伸展树属于一种平衡树,也是一棵普通的二叉排序树。

2.伸展树的核心在于它的伸展(splay)操作,对于每一次的伸展操作(把某个节点放到目标节点的下面),都有可能改变树中每个节点的分布,从而改变整个树的形状。

3.伸展树对于树的平横性没有要求,与平衡树不同,任意两个节点都可以有任意的深度差,不需要记录平衡树的冗余信息。

4.伸展树每次搜索的复杂度平摊下来都是log(n),如果遇到插入的数每次都是两个极端的情况,此时伸展树退化为链状,复杂度最坏。

5.伸展树的旋转操作

伸展树中的旋转操作不同于平衡树,一般对于这个操作最基本的方法就是一层一层的向上旋转,无法改变树的形态,而伸展树中不需要

控制树的形态,从而有新的方法来进行旋转。

在伸展树的旋转操作一共可以分为三类(按形状分),每类都有镜像。

1 )单旋转:当前节点的父节点即为目标节点,那么直接左旋或者右旋即可。

         goal           x        /       ->       \       x                  goal

2)一字型:顾名思义树枝的形状呈现为一字型,如图

         z                      x        /            y           \       y      ->    / \   ->      y      /            x   z           \     x                              z
此时先对y进行旋转,再对x进行旋转


3)之字形:顾名思义树枝的形状呈现为之字形,如图

          z            z                   /            /            x       y      ->    x     ->     / \        \          /            y   z         x        y             
此时先对x旋转,之后再对x进行一次旋转


以上的旋转左旋还是右旋辨别有一个小窍门,对于x来说,如果是y的左节点,那么右旋,如果是y的右节点,那么左旋,

一字型的两次旋转方向相同,之字形相反。


-------------------------------------以上是基础必备知识------------------------------------------

那么我们如何像线段树一样运用伸展树对数列的区间经行操作呢?

先来看一道题目


A Simple Problem with Integers

Description

给出了一个序列,你需要处理如下两种询问。

"C a b c"表示给[a, b]区间中的值全部增加c (-10000 c  10000)

"Q a b" 询问[a, b]区间中所有值的和。

Input

第一行包含两个整数N,Q。1 N,Q  100000.

第二行包含n个整数,表示初始的序列A (-1000000000 Ai 1000000000)。

接下来Q行询问,格式如题目描述。

Output

对于每一个Q开头的询问,你需要输出相应的答案,每个答案一行。

Sample Input

10 51 2 3 4 5 6 7 8 9 10Q 4 4Q 1 10Q 2 4C 3 6 3Q 2 4

Sample Output

455915


题意很明确,就是给出更新操作和查询操作让我们来执行,用线段树来写很方便,效率很高,但是如果用伸展树该如何解决。

首先我们知道伸展树的中序序列就是我们要维护的区间,那么我们假设现在要维护的区间为[a,b],那么如果a-1号节点的右儿子刚好是b+1,那么此时我们的区间就刚好是

b+1号节点的左儿子?如图


为了防止b+1不能直接转移到a-1的右儿子上,我们直接把a-1放到根节点上,继而b+1号节点就能顺理成章的转移到a-1的右节点上

这样以来区间就能确定了,但是由于a-1和b+1都有有可能越界,我们需要另外虚设两个节点防止越界,上代码:


#define keytree ch[ch[root][1]][0]void newnode(int &x,int p,int v){    x=++top;//为每个节点分配编号    ch[x][0]=ch[x][1]=0;//每个节点末端初始化为终端节点    pre[x]=p;    val[x]=v;    add[x]=0;    sum[x]=v;    siz[x]=1;} 


void init(int n){    for(int i=0; i<n; i++) scanf("%d",&tmp[i]);    root=top=0;    siz[0]=ch[0][0]=ch[0][1]=add[0]=sum[0]=pre[0]=0;//0为终端节点    newnode(root,0,-1);//虚设节点    newnode(ch[root][1],root,-1);//虚设节点    build(keytree,0,n-1,ch[root][1]);    pushup(ch[root][1]);    pushup(root);}
两个虚设的节点在整个数列的两端维护整个数列

对于0-n-1个结点来说,为了从一开始就尽可能减少复杂度,我们从中间节点开始建树,如代码:

void build(int &x,int l,int r,int p){    int mid=(l+r)>>1;    newnode(x,p,tmp[mid]);    if(l<mid) build(ch[x][0],l,mid-1,x);    if(r>mid) build(ch[x][1],mid+1,r,x);    pushup(x);}
那么样例中的树建好后应该是这样


图中的数字代表的是节点的编号,而不是原数列中的下标,不要被迷惑了。

每次的区间就被安排在了keytree那个位置

对于图中的的每一个节点来说,他在序列中对应的编号就是它的左树节点个数+1,在找第原序列中的第几号时按照的就是这个规律。


题目源代码:

#include<cstring>#include<cstdio>#include<iostream>#include<algorithm>using namespace std;#define maxn 100020#define keytree ch[ch[root][1]][0] ///keytree代表splay之后的区间节点int ch[maxn][2],pre[maxn],tmp[maxn],val[maxn],siz[maxn],add[maxn]; ///ch用来存储节点的左孩子右孩子,add为延迟标记,siz代表子树中有多少个节点long long sum[maxn];int top,root;/*debug部分void travel(int r){    if(r)    {        travel(ch[r][0]);        printf("node: %2d l: %2d r: %2d pre: %2d val: %2d siz: %2d add: %2d sum: %2d\n",r,ch[r][0],ch[r][1],pre[r],val[r],siz[r],add[r],sum[r]);        travel(ch[r][1]);    }}void debug(){    printf("root= %d\n",root);    travel(root);}*////newnode部分主要功能是为每个节点分配一个编号和初始化该节点信息,注意这里的引用void newnode(int &x,int p,int v){    x=++top;    ch[x][0]=ch[x][1]=0;    pre[x]=p;    val[x]=v;    add[x]=0;    sum[x]=v;    siz[x]=1;}///和线段树一样的操作void pushup(int x){    siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;    sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];}void pushdown(int x){    if(add[x])    {        add[ch[x][0]]+=add[x];        add[ch[x][1]]+=add[x];        val[ch[x][0]]+=add[x];        val[ch[x][1]]+=add[x];        sum[ch[x][0]]+=(long long)siz[ch[x][0]]*add[x];        sum[ch[x][1]]+=(long long)siz[ch[x][1]]*add[x];        add[x]=0;    }}///由数列的中间开始建树void build(int &x,int l,int r,int p){    int mid=(l+r)>>1;    newnode(x,p,tmp[mid]);    if(l<mid) build(ch[x][0],l,mid-1,x);    if(r>mid) build(ch[x][1],mid+1,r,x);    pushup(x);}///初始化终端节点,申请两个虚拟节点,建树void init(int n){    for(int i=0; i<n; i++) scanf("%d",&tmp[i]);    root=top=0;    siz[0]=ch[0][0]=ch[0][1]=add[0]=sum[0]=pre[0]=0;    newnode(root,0,-1);    newnode(ch[root][1],root,-1);    build(keytree,0,n-1,ch[root][1]);    pushup(ch[root][1]);    pushup(root);}///旋转操作,kind代表旋转方式void Rotate(int x,int kind){    int y=pre[x];    pushdown(y);    pushdown(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;    pushup(y);}///将r节点旋转到goal下面,自底向上的旋转void splay(int r,int goal){    pushdown(r);    while(pre[r]!=goal)    {        if(pre[pre[r]]==goal)            Rotate(r,ch[pre[r]][0]==r);        else        {            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);            }        }    }    pushup(r);    if(goal==0) root=r;}///根据siz的特征找到第k号节点int get_kth(int r,int k){    pushdown(r);    int t=siz[ch[r][0]]+1;///注意这里的+1    if(t==k) return r;    if(k<t) get_kth(ch[r][0],k);    else get_kth(ch[r][1],k-t);}///由于多了两个节点,所以每次将l旋转到0下面,r+2旋转到root下面,///这样才能准确的确定keytree就是要找的区间,结合图形和get_kth想long long query(int l,int r){    splay(get_kth(root,l),0);    splay(get_kth(root,r+2),root);    return sum[keytree];}void update(int l,int r,int d){    splay(get_kth(root,l),0);    splay(get_kth(root,r+2),root);    add[keytree]+=d;    val[keytree]+=d;    sum[keytree]+=(long long)siz[keytree]*d;    pushup(ch[root][1]);    pushup(root);}int main(){    int n,q;    while(scanf("%d%d",&n,&q)!=EOF)    {        init(n);        char str[10];        while(q--)        {         //   debug();            int a,b,c;            scanf("%s",str);            if(str[0]=='Q')            {                scanf("%d%d",&a,&b);                long long ans=query(a,b);                printf("%I64d\n",ans);            }            else            {                scanf("%d%d%d",&a,&b,&c);                update(a,b,c);            }        }    }} 


0 0
原创粉丝点击