现在的位置: 首页 > 综合 > 正文

BZOJ 1588 营业额统计(Splay树入门)

2019年02月11日 ⁄ 综合 ⁄ 共 2597字 ⁄ 字号 评论关闭

题意:给出一组数,设定每个数与其前面的数之间最小相距值的绝对值为最小波动值(第一个数的最小波动值为其本身)。求所有最小波动值之和。


Splay入门题。此题仅包含插入,旋转和伸展操作。

可先参考Crash前辈的论文《运用伸展树解决数列维护问题》

 

首先是旋转操作。

和一般的平衡树旋转操作类似,分为左旋和右旋。

将左儿子旋转至根,需要执行右旋操作。

旋转前:(x为左儿子,y为根节点)                                              旋转后:

                             
                                     

将右儿子旋转至根节点,需要执行左旋操作。

旋转前:(y为右儿子,x为根节点)                                             旋转后:

     
                                                          

显然,执行x旋操作,就是将!x儿子旋转至根节点,并把儿子的x子树替换给原根节点。

 

如果希望是根节点的儿子的儿子被旋转至根节点,则需要执行一字形操作或是之字形操作。

一字形:


例如右旋一字形,目的是将浅蓝色节点旋转至树根:首先将浅紫色节点旋转至根(右旋),然后将浅蓝色节点旋转至根(右旋)。


之字形:(仅显示一种,另一种对称)


(举例图上例子)

首先将绿色节点旋转至其父亲节点位置(右旋);然后再旋转一次绿色节点(左旋)。

(两次都是旋转绿色节点)


接下来是伸展操作:

伸展操作与旋转操作联系十分紧密,伸展操作的目的,是将某节点x,通过一层层旋转,变成另外一个节点f的子节点。

 

假令x的父亲为y,y的父亲为z。

如果y的父亲已经是f,则只需一次单旋转;

如果不是则继续查询x,y和z之间的关系,以此来决定是一字形旋转,还是之字形旋转,然后将x挪至z;

继续前面的操作,直至x的父亲变为f。

 

接下来是插入操作和查询操作。

对于本题,每个数字都需要查询当前时刻,在数值上与其最相邻的数是什么。

此处则是巧妙运用Splay树性质的地方了:中序遍历一棵splay树,即是这个数组的顺序。

此题中,Splay树维护着一个按大小排序的数组。

显然最方便的操作是:插入一个数时,即将这个数旋转至根。这样查询遍十分方便。

 

深搜(左子节点小于父亲,右子节点大于父亲,不保存相同的值)当查询至某节点的值正好等于插入值时,仅需伸展一次(很大的可能性可以使得树更加平衡);

当走至树的叶子节点也没有找到该插入值时,建立一个新点,插入该叶子节点的左子节点(值小于父亲节点)或右子节点(值大于父亲节点),并将插入的点旋转至树根处。

 

接下来按照中序遍历的性质即可,左相邻值是左子树的最右点,右相邻值是右子树的最左点。

#include <algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
using namespace std;
#define MAXN 100010
#define INF 0x3f3f3f3f

struct SplayTree
{
    int fa[MAXN], ch[MAXN][2], val[MAXN];
    int cnt, root;
    void New(int &t, int f, int v)
    {
        t = ++cnt;
        fa[t] = f, val[t] = v;
        ch[t][0] = ch[t][0] = 0;
    }
    //旋转操作,c = 0表示左旋,c = 1表示右旋
    void Rotate(int x, int c)
    {
        int y = fa[x];
        int z = fa[y];
        ch[y][!c] = ch[x][c];
        if(ch[x][c]) fa[ch[x][c]] = y;
        fa[x] = fa[y];
        if(fa[y])
        {
            if(ch[z][0] == y) ch[z][0] = x;
            else ch[z][1] = x;
        }
        ch[x][c] = y, fa[y] = x;
        if(y == root) root = x;
    }
    //Splay操作,表示把结点x转到结点f的下面
    void Splay(int x, int f)
    {
        while(fa[x] != f)
        {
            int y = fa[x];
            int z = fa[y];
            //父节点即为f,执行单旋转
            if(fa[y] == f)
            {
                if(ch[y][0] == x) Rotate(x, 1);
                else Rotate(x, 0);
            }
            else
            {
                if(ch[z][0] == y)
                {
                    if(ch[y][0] == x) Rotate(y, 1), Rotate(x, 1); //一字型旋转
                    else Rotate(x, 0), Rotate(x, 1); //之字形旋转
                }
                else
                {
                    if(ch[y][1] == x) Rotate(y, 0), Rotate(x, 0); //一字形旋转
                    else Rotate(x, 1), Rotate(x, 0); //之字形旋转
                }
            }
        }
        if(f == 0) root = x;
    }
    int l()
    {
        int t = root;
        if(ch[t][0] == 0) return INF;
        t = ch[t][0];
        while(ch[t][1]) t = ch[t][1];
        return val[t];
    }
    int r()
    {
        int t = root;
        if(ch[t][1] == 0) return INF;
        t = ch[t][1];
        while(ch[t][0]) t = ch[t][0];
        return val[t];
    }
    int Insert(int k)
    {
        int r = root, c;
        while(true)
        {
            if(val[r] == k)
            {
                Splay(r, 0);
                return 0;
            }
            c = k < val[r] ? 0 : 1;
            if(ch[r][c] == 0) break;
            r = ch[r][c];
        }
        c = k < val[r] ? 0 : 1;
        New(ch[r][c], r, k);
        Splay(ch[r][c], 0);
        return 1;
    }
}splay;

int main()
{
    int n;
    while(~scanf("%d", &n))
    {
        int ans = 0;
        splay.root = splay.cnt = 0;
        for(int i = 0; i < n; i++)
        {
            int x = 0;
            scanf("%d", &x);
            if(!i)
            {
                ans += x;
                splay.New(splay.root, 0, x);
            }
            else
            {
                int t = splay.Insert(x);
                if(!t) continue;
                int l = splay.l();
                int r = splay.r();
                ans += min(abs(l - x), abs(r - x));
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

抱歉!评论已关闭.