您的位置:首页 > 其它

伸展树的构建和最基本的插入与查询

2015-08-13 11:07 225 查看
题目链接:http://acm.tzc.edu.cn/acmhome/problemdetail.do?method=showdetail&id=2924

描述

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

账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现

一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。

经济管理学上定义了一种最小波动值来衡量这种情况:

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

当最小波动值越大时,就说明营业情况越不稳定。而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。

你的任务就是编写一个程序帮助Tiger来计算这一个值。第一天的最小波动值为第一天的营业额。

输入

测试数据多组,每组的第一行为正整数n(1 <= n <= 32767), 表示该公司从成立一直到现在的天数. 接下来的n行每行有一个整数Ai(Ai <= 1000000) ,

表示第i天的营业额。处理到EOF为止。

输出

每组数据占一行,每行输出一个整数,每天最小波动值的和。结果小于2^31

样例输入

6

5

1

2

5

4

6

样例输出

12

提示

5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12

思路:每次输入都找出之前与当前输入最接近的那个数,求出差的绝对值加到答案里。此题若用最暴力的方法,每次输入都从头循环一次,必然能求出答案,

但是数据量有点大,会超时。所以这里用伸展树求解,时间复杂度会大大降低。

关于伸展树若不了解的话,我推荐kernel@hcy的博客:/article/5853733.html

这篇博客详细的说了伸展树的所有基本操作,在此我不再赘述,我只说一个我们做这题会利用到的特点。

伸展树:伸展树有一个最基本的特点,就是其左子树的所有节点的值一定比父节点小,其右子树的的所有节点的值一定比父节点大。

那么,我们如果每次读入,都把其放到根节点,那么我们只要找到左子树中最大的值,和右子树中最小的值,比较他们和当前输入的差的绝对值,更小的那个就是

当天的波动值了。

下面是AC代码(记得用G++提交,另外建议看完我推荐的那篇博客再来看代码,熟悉了那里的基本操作(通过左旋,右旋,更新根节点),才能看懂下面代码)

<pre name="code" class="cpp">#include<iostream>
#include <cstring>
#include <cstdio>
#define maxn 111111
using namespace std;
int n;
int root,tot;//root就是根节点的标号,tot记录节点总数量
int ans;
int pre[maxn],key[maxn],ch[maxn][2];//pre数组记录父节点的标号,key数组记录节点的值,ch记录其左右子节点的标号。标号从1开始,到总天数n
void NewNode(int &r,int father,int k)//建立一个新的节点
{
r=++tot;//根的标号是r,要传指针,每次都要更新标号
pre[r]=father;//father为父节点的标号,0则表示空,没有父节点
key[r]=k;//记录r标号的值
ch[r][0]=ch[r][1]=0;//其左右子节点为空,0表示左子节点,1表示右子节点
}
void Rotate(int x,int kind)
{
int y=pre[x];
ch[y][!kind]=ch[x][kind];//根据旋转特性,x的右子树变成y的左子树,或其左子树变成y的右子树,相当于y认了个儿子=,=
pre[ch[x][kind]]=y;//将要改变的x的子树的父节点换成y,相当于儿子承认了上面那个父亲=,=
if(pre[y])
ch[pre[y]][ch[pre[y]][1]==y]=x;//如果父节点不是根结点,则要和父节点的父节点连接起来
pre[x]=pre[y];//y和x的位置要换,所以y的父节点便成x的父节点
ch[x][kind]=y;//x变成y的父节点,认y做儿子
pre[y]=x;//y的父节点变成x,y也承认了x这个父亲=,=
}
void Splay(int r,int goal)//看Splay代码前看看我推荐的博客,看懂了对下面的操作会更容易理解
{
while(pre[r]!=goal)//本身不是根节点,则要将其旋转成根节点
{
if(pre[pre[r]]==0)//若其父节点就是根节点,则直接对r旋转一次
Rotate(r,ch[pre[r]][0]==r);
else{
int y=pre[r];//记录父节点的标号
int kind=ch[pre[y]][0]==y;//kind用于判断是进行两次同样的旋转还是不同的旋转
if(ch[y][kind]==r)//三个节点不在同一条线上,先左旋后右旋或者先右旋后左旋
{
Rotate(r,!kind);
Rotate(r,kind);
}else{
Rotate(y,kind);//在同一条线上,连续两次左旋或连续两次右旋
Rotate(r,kind);
}
}
}
root=r;//根节点更新成为当前的节点标号,当前的节点已经旋转成根节点
}
int Insert(int k)
{
int r=root;//记录当前标号
while(ch[r][key[r]<k])//若r的子节点存在,则一直向下查找是否有值为k的,有则返回0
{
if(key[r]==k)
{
Splay(r,0);
return 0;
}
r=ch[r][key[r]<k];//key[r]<k若成立,则返回1,表示的是r的右子节点,否则为左子节点
}
NewNode(ch[r][k>key[r]],r,k);//没有找到相等的值,则建一个节点,将其插入伸展树
Splay(ch[r][k>key[r]],0);//开始判断情况,将新插入的点旋转到根节点
return 1;
}

int get_next(int x)//找右子树最小的值
{
int temp=ch[x][1];//记录右子树
if(!temp)return 999999999;//若右子树不存在,返回无穷大
while(ch[temp][0])//当右子树的左子树存在,则更新temp的值
temp=ch[temp][0];
return key[temp]-key[x];//返回波动值
}
int get_pre(int x)//同上
{
int temp=ch[x][0];
if(!temp)return 999999999;
while(ch[temp][1])
temp=ch[temp][1];
return key[x]-key[temp];
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
ans=root=tot=0;
int num;
for(int i=1;i<=n;i++)
{
scanf("%d",&num);
if(i==1)
{
ans+=num;//第一天直接加
NewNode(root,0,num);//建立第一个节点
continue;
}
if(!Insert(num))continue;//判断是否输入过相同的值,若有,则当天波动值一定是0,不用再查找
int a=get_next(root);//若没有输入过相等的值,则查找其左子树中的最大值和右子树中的最小值,求出它们和当天值的差的绝对值,更小的那个为当天波动值
int b=get_pre(root);
ans+=min(a,b);
}
printf("%d\n",ans);
}
return 0;
}



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: