首页 > 代码库 > wikioi 1396 伸展树(模板)

wikioi 1396 伸展树(模板)

题目描述 Description

Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。

Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:

该天的最小波动值 = min{|该天以前某一天的营业额-该天营业额|}

当最小波动值越大时,就说明营业情况越不稳定。

而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助Tiger来计算这一个值。

第一天的最小波动值为第一天的营业额。

输入描述 Input Description

第一行为正整数n(n<=32767),表示该公司从成立一直到现在的天数,接下来的n行每行有一个正整数ai(ai<=1000000),表示第i天公司的营业额。

输出描述 Output Description

输出文件仅有一个正整数,即每天最小波动值之和,小于231

样例输入 Sample Input

6

5

1

2

5

4

6

样例输出 Sample Output

12

数据范围及提示 Data Size & Hint

结果说明:5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12


思路:伸展树从下午开始看,以前看过了,觉得太麻烦了,然后数据结构老师讲过之后,我学明白了,考试都秒过,然后现在了才开始做题。因为在数据结构实现中,本人比较喜欢数组替代指针,所以尼玛找了好久的模板才找到这个让自己觉得很爽又很能理解的。其他都是指针来实现,代码又长,烦死了。

因为找了一下午加一晚上,实在找不到数组实现的伸展树了,就查了这个入门题的解题报告,终于让我发现了这个好代码,嘿嘿。

参考代码博客:http://blog.csdn.net/rowanhaoa/article/details/24436703

说明:伸展树是建立在排序二叉树上的,所以左子树所有的值都比根小,根又都比右子树所有的值小。因为伸展树是平衡二叉树,所以具备平衡二叉树的特点,所有功能的平摊时间复杂度都是O(log n),所以在数据结构中用得非常爽,代码又是可以接受的复杂度和长度。而且每个结点也可以是区间,可以代替线段树,你说爽不爽。伸展树最大的优点是分裂和合并都是log n,所以比别的当然很快,在处理很多问题上自然有很多优点。从此,爱上伸展树……

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<bitset>
#define mem(a,b) memset(a,b,sizeof(a))
#define INF 1000000007
#define maxn 100010
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
struct splaytree //伸展树
{
    int pre[maxn];//前驱
    int key[maxn];//键值
    int ch[maxn][2];//左右孩子,0为左孩子,1为右孩子
    int root,tot;//根节点,节点数量
    splaytree()
    {
        tot=root=0;
        mem(ch,0);
        mem(pre,0);
        mem(key,0);
    }
    void newnode(int &r,int father,int k)
    {//在r位置,父亲为father,新建一个值点。
        r=++tot;
        pre[r]=father;
        key[r]=k;
        ch[r][0]=ch[r][1]=0;
    }
    void rotate(int x,int kind)//kind=1表示右旋,kind=0表示左旋
    {
        int y=pre[x];
        ch[y][!kind]=ch[x][kind];
        pre[ch[x][kind]]=y;
        ch[x][kind]=y;
        if(pre[y]) ch[pre[y]][ch[pre[y]][1]==y]=x;
        pre[x]=pre[y];
        pre[y]=x;
    }
    void splay(int x,int goal) //把x伸展到目标位置
    {
        while(pre[x]!=goal)
        {
            if(pre[pre[x]]==goal) rotate(x,ch[pre[x]][0]==x);
            else
            {
                int y=pre[x];
                int kind=ch[pre[y]][0]==y;
                if(ch[y][kind]==x) rotate(x,!kind),rotate(x,kind);
                else rotate(y,kind),rotate(x,kind);
            }
        }
        if(!goal) root=x;//把root更新成x
    }
    int insert(int x)//插入值x
    {
        int r=root;
        while(ch[r][key[r]<x])
        {
            if(key[r]==x) {splay(r,0);return 0;}
            r=ch[r][key[r]<x];
        }
        newnode(ch[r][x>key[r]],r,x);
        splay(ch[r][x>key[r]],0);
        return 1;
    }
    int find(int x)//查找键值为x的结点编号
    {
        int r=root;
        if(!r) return -INF;//树未建,未找到
        if(key[r]==x) return r;
        while(ch[r][x>key[r]])
        {
            r=ch[r][x>key[r]];
            if(key[r]==x) return r;
        }
        return -INF;//未找到
    }
    int get_pre(int x)//寻找节点x的前驱的值,未找到则返回INF
    {
        int r=ch[x][0];
        if(!r) return -INF;
        while(ch[r][1]) r=ch[r][1];
        return key[r];
    }
    int get_next(int x)
    {
        int r=ch[x][1];
        if(!r) return INF;
        while(ch[r][0]) r=ch[r][0];
        return key[r];
    }
};
int main()
{
    int sum=0,a,n;
    splaytree tree;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a);
        if(!i)
        {
            sum+=a;
            tree.newnode(tree.root,0,a);
            continue;
        }
        if(!tree.insert(a)) continue;
        //因为既然前面已经有a,那这天的相减为0,所以下面就不用继续了
        int x=tree.get_pre(tree.root);//因为insert之后根root即为当前a的
        int y=tree.get_next(tree.root);//故可从根查找
        sum+=min(a-x,y-a);
    }
    cout<<sum<<endl;
    return 0;
}