zoukankan      html  css  js  c++  java
  • 数据结构之splay树

    https://www.bilibili.com/video/av19879546
    https://blog.csdn.net/u014634338/article/details/42465089
    kuangbin
    https://blog.csdn.net/changtao381/article/details/8936765

    类别:二叉排序树
    空间效率:O(n)
    时间效率:O(logn)
    优点:每次查询会调整树的结构,使被查询平率高的条目靠近树的根。

    关键操作是Splay
    1、A Simple Problem with Integers

    Time Limit: 5000MS Memory Limit: 131072K Total Submissions:
    128343 Accepted: 39841 Case Time Limit: 2000MS

    Description

    You have N integers, A1, A2, … , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.

    Input

    The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000. The
    second line contains N numbers, the initial values of A1, A2, … ,
    AN. -1000000000 ≤ Ai ≤ 1000000000. Each of the next Q lines represents
    an operation. “C a b c” means adding c to each of Aa, Aa+1, … , Ab.
    -10000 ≤ c ≤ 10000. “Q a b” means querying the sum of Aa, Aa+1, … , Ab.

    Output

    You need to answer all Q commands in order. One answer in a line.

    Sample Input

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

    Sample Output

    4
    55
    9
    15

    解题代码:

    /*
     * POJ 3468 A Simple Problem with Integers
     * 经典的线段树题目,用splay tree来作为入门题
     * 成段更新+区间求和
     * 题目给定了n个数A1,A2,...An,有以下两种操作
     * C a b c:把c加入到Aa,Aa+1,..Ab中
     * Q a b:查询Aa,Aa+1,..Ab的和
     * 需要的变量:pre,ch,size(这三个基本都要),key(保存结点的值),sum(子树值和),add(增量的标记)
     * (一般标记类,正确的做法都是要先更新掉该点,标记是标记没有更新子结点)
     */
    
    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    using namespace std;
    #define Key_value ch[ch[root][1]][0]
    const int MAXN=100010;
    int pre[MAXN],ch[MAXN][2],size[MAXN],root,tot1;//父结点、左右孩子、子树规模、根结点、结点数量
    int key[MAXN];//该点的值
    int add[MAXN];//增量的延迟标记
    long long sum[MAXN];//子树的和
    int s[MAXN],tot2;//内存池、内存池容量(这题用不到,如果有删除操作,内存不够可以这样
    
    int a[MAXN];//初始的数组,建树时候用
    int n,q;
    //debug部分
    void Treavel(int x)
    {
        if(x)
        {
            Treavel(ch[x][0]);
            printf("结点%2d:左儿子 %2d 右儿子 %2d 父结点 %2d size=%2d,key=%2d add=%2d sum=%I64d
    ",x,ch[x][0],ch[x][1],pre[x],size[x],key[x],add[x],sum[x]);
            Treavel(ch[x][1]);
        }
    }
    void debug()
    {
        printf("root:%d
    ",root);
        Treavel(root);
    }
    //以上是debug
    void NewNode(int &r,int father,int k)//一个是调用的时候注意变量顺序,还有r必须引用&
    {
        if(tot2)r=s[tot2--];//取得时候是tot2--,那么存的时候就要是++tot2
        else r=++tot1;
        pre[r]=father;
        size[r]=1;//这个不能忘记 ,一定是1,否则可能出错
        key[r]=k;
        add[r]=0;
        sum[r]=0;
        ch[r][0]=ch[r][1]=0;
    }
    //给r为根的子树增加值,一定把当前结点的全部更新掉,再加个延迟标记表示儿子结点没有更新
    void Update_Add(int r,int ADD)
    {
        if(r==0)return;
        add[r]+=ADD;
        key[r]+=ADD;
        sum[r]+=(long long)ADD*size[r];
    }
    //通过孩子结点更新父亲结点
    void Push_Up(int r)
    {
        size[r]=size[ch[r][0]]+size[ch[r][1]]+1;
        sum[r]=sum[ch[r][0]]+sum[ch[r][1]]+key[r];
    }
    //将延迟标记更新到孩子结点
    void Push_Down(int r)
    {
        if(add[r])
        {
            Update_Add(ch[r][0],add[r]);
            Update_Add(ch[r][1],add[r]);
            add[r]=0;
        }
    }
    //建树
    //先建立中间结点,再两端的方法
    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);
        Push_Up(x);
    }
    //初始化,前后各加一个king结点
    void Init()
    {
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        root=tot1=tot2=0;
        ch[root][0]=ch[root][1]=pre[root]=size[root]=add[root]=sum[root]=0;
        key[root]=0;
        NewNode(root,0,-1);
        NewNode(ch[root][1],root,-1);//头尾各加入一个空位
        Build(Key_value,1,n,ch[root][1]);
        Push_Up(ch[root][1]);
        Push_Up(root);
    }
    //旋转,0为左旋,1为右旋  该部分基本固定
    void Rotate(int x,int kind)
    {
        int y=pre[x];
        Push_Down(y);
        Push_Down(x);//先把y的标记向下传递,再把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);//维护y结点
    }
    //Splay调整,将结点r调整到goal下面
    void Splay(int r,int goal)
    {
        Push_Down(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);
                }
            }
        }
        Push_Up(r);
        if(goal==0)root=r;
    }
    //得到第k个结点
    int Get_Kth(int r,int k)
    {
        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;
    }
    //区间增加一个值
    //注意因为在前面增加了个结点,所以把第l个结点旋转到根结点,第r+2个结点旋转到根结点的右孩子,
    //那么Key_value(ch[ch[root][1]][0]刚好就是区间[l,r]
    void ADD(int l,int r,int D)
    {
        Splay(Get_Kth(root,l),0);//第l个点到根结点
        Splay(Get_Kth(root,r+2),root);//第r+2个点到根结点的右孩子
        Update_Add(Key_value,D);
        Push_Up(ch[root][1]);
        Push_Up(root);
    }
    //查询区间的和
    long long Query_Sum(int l,int r)
    {
        Splay(Get_Kth(root,l),0);//第l个点到根结点
        Splay(Get_Kth(root,r+2),root);//第r+2个点到根结点的右孩子
        return sum[Key_value];
    }
    
    int main()
    {
        //freopen("in.txt","r",stdin);
        //freopen("out.txt","w",stdout);
        while(scanf("%d%d",&n,&q)==2)
        {
            Init();//这个不能忘记
            while(q--)
            {
                char op[20];
                int x,y,z;
                scanf("%s",op);
                if(op[0]=='Q')
                {
                    scanf("%d%d",&x,&y);
                    printf("%I64d
    ",Query_Sum(x,y));
                }
                else
                {
                    scanf("%d%d%d",&x,&y,&z);
                    ADD(x,y,z);
                }
            }
        }
        return 0;
    }
    
  • 相关阅读:
    java接口鉴权之sign签名校验与JWT验证
    完整的后端开发流程-深入浅出Java线程池:使用篇
    Java多线程加法计算--Java识别静态验证码和动态验证码
    超全面设计指南:如何做大屏数据可视化设计?
    vue+echarts+datav大屏数据展示及实现中国地图省市县下钻
    开源」目前见过的最好的开源OA产品架构师之路(一):何时选用合适的语言
    如何做大屏数据可视化设计?
    Spring项目方便调试打印请求信息点击跳转到方法
    Echart生成的报表导出为PDF
    java环境变量一键配置
  • 原文地址:https://www.cnblogs.com/bryce1010/p/9386981.html
Copyright © 2011-2022 走看看