BZOJ 1588 营业额统计 (Splay Tree)

题目大意:

中文题面= =


大致思路:

作为初学Splay的第一道练习题.....

使用到的操作:

1.在原序列末尾插入新元素, 键值不出现重复

2. 寻找比某个键值小或者大的最近的键值, 通过Splay转为求根节点的前驱和后继


代码如下:

Result  :  Accepted     Memory  :  2836 KB     Time  :  140 ms

/*
 * Author: Gatevin
 * Created Time:  2015/8/21 15:30:30
 * File Name: Sakura_Chiyo.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

struct Splay_Tree
{
#define maxn 100010
    int pre[maxn];//u父亲节点是pre[u]
    int key[maxn];//键值
    int next[maxn][2];//next[u][0]是结点u的左子树, next[u][1]是u的右子树
    int root;//当前的根节点
    int tot;//总的结点数量
    void newnode(int &r, int father, int value)//新建结点, 父亲是father, 键值为value
    {
        r = ++tot;
        pre[r] = father;
        key[r] = value;
        next[r][0] = next[r][1] = 0;//新节点r的左右子树为空
        return;
    }
    
    //旋转, kind = 1表示右旋, kind = 0表示左旋
    void Rotate(int x, int kind)
    {
        int y = pre[x];
        next[y][!kind] = next[x][kind];
        pre[next[x][kind]] = y;
        if(pre[y])
            next[pre[y]][next[pre[y]][1] == y] = x;
        pre[x] = pre[y];
        next[x][kind] = y;
        pre[y] = x;
        return;
    }
    
    //Splay伸展调整, 将根为r的子树调整为goal, (将r所在子树调整为goal的左或者右子树
    void Splay(int r, int goal)//goal为0会旋转至根节点, pre[root] = 0
    {
        //旋转会使得r的父亲是goal
        while(pre[r] != goal)
        {
            if(pre[pre[r]] == goal)//只需要进行一次Zig或者Zag操作即可
                Rotate(r, next[pre[r]][0] == r);//r是左子树就右旋, 否则左旋
            else
            {
                int y = pre[r];
                int kind = next[pre[y]][0] == y;//y是否是左子树
                
                if(next[y][kind] == r)//y左r右或者 y右r做, 说明要进行Zig-Zag或者Zag-Zig操作
                {
                    Rotate(r, !kind);
                    Rotate(r, kind);
                }
                else//y, r都是左子树或者都是右子树, 进行Zig-Zig或者Zag-Zag操作
                {
                    Rotate(y, kind);
                    Rotate(r, kind);
                }
            }
        }
        if(goal == 0) root = r;
    }
    
    int insert(int k)//插入一个键值k
    {
        int r = root;
        while(next[r][key[r] < k])
        {
            if(key[r] == k)//重复元素, 不插入
            {
                Splay(r, 0);//将r旋转至根节点
                return 0;//插入失败
            }
            r = next[r][key[r] < k];
        }
        newnode(next[r][k > key[r]], r, k);
        Splay(next[r][k > key[r]], 0);//将新插入的结点旋转至根
        return 1;//插入成功
    }
    
    //寻找结点编号为x的结点的前驱结点, 即左子树的最右结点
    int getPre(int x)//返回结点编号, -1表示没有
    {
        int tmp = next[x][0];
        if(tmp == 0) return -1;
        while(next[tmp][1])
            tmp = next[tmp][1];
        return tmp;//
    }
    
    //寻找结点编号为x的结点的后继结点, 即右子树的最左结点
    int getNext(int x)//返回结点编号, 编号为-1表示没有
    {
        int tmp = next[x][1];
        if(tmp == 0) return -1;
        while(next[tmp][0])
            tmp = next[tmp][0];
        return tmp;
    }
};

Splay_Tree tree;

int n;
const int inf = 1e9 + 7;

int main()
{
    while(~scanf("%d", &n))
    {
        tree.root = tree.tot = 0;
        int ans = 0;
        for(int i = 1; i <= n; i++)
        {
            int num;
            if(scanf("%d", &num) == EOF) num = 0;//莫名的奇怪判断...
            if(i == 1)
            {
                ans += num;
                tree.newnode(tree.root, 0, num);
                continue;
            }
            if(tree.insert(num) == 0) continue;//重复值
            //由于进行了insert操作num一定在根的位置
            int tmp = inf;
            int a = tree.getPre(tree.root);
            int b = tree.getNext(tree.root);
            if(a != -1) tmp = min(tmp, tree.key[tree.root] - tree.key[a]);
            if(b != -1) tmp = min(tmp, tree.key[b] - tree.key[tree.root]);
            ans += tmp;
        }
        printf("%d\n", ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值