又是一道各大OJ上都有的题,大家随意
营业额统计
描述
Tiger 最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。
Tiger 拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。
经济管理学上定义了一种最小波动值来衡量这种情况:记该天以前某一天的营业额为 ai,该天营业额为 b,则该天的最小波动值 δ=min|ai−b|,当最小波动值越大时,就说明营业情况越不稳定。而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。
你的任务就是编写一个程序帮助 Tiger 来计算这一个值,第一天的最小波动值为第一天的营业额。
一句话题意 给出一个 n 个数的数列{an},对于第 i个元素 ai,定义 fi=min|ai−aj|,其中 1≤j<i,f1=a1。求 ∑fi
输入
第一行为正整数,表示该公司从成立一直到现在的天数;
接下来的 n 行每行有一个正整数,表示第 i 天公司的营业额 ai
输出
仅有一个正整数,即每一天最小波动的和,结果不超过 2e31
样例输入
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
对于全部数据,1≤n<2e15,ai≤10^6
分析
啊啊啊啊啊,终于A了!!!!!本蒟蒻高兴得都想要好好写一下这篇博客了
首先感谢万分良心的LOJ,得益于它提供的数据,我发现了代码的很多问题Orz
其次,开始讲吧
每次插入一个数,同时将调整到根的位置,然后寻找前驱和后继,比较差值大小即可
是不是很简单,很模板啊
我们来看代码吧
代码
没加注释的地方就是splay的常规操作了
#include<bits/stdc++.h>
#define N 200000
#define in read()
using namespace std;
int n,fa[N],ch[N][2],sze[N],rt=0;
int a[N],tot=0;
inline int read(){
char ch;int f=1,res=0;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
while(ch>='0'&&ch<='9'){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
return f==1?res:-res;
}
void pushup(int x){ sze[x]=sze[ch[x][0]]+sze[ch[x][1]]+1;}
int which(int x){ return x==ch[fa[x]][1];}
void rotate(int x,int &rt){
int y=fa[x],z=fa[y],d=which(x),d1=which(y);
if(y==rt) rt=x;else ch[z][d1]=x;
ch[y][d]=ch[x][d^1];fa[ch[x][d^1]]=y;
ch[x][d^1]=y;fa[y]=x;fa[x]=z;
if(y) pushup(y);
if(x) pushup(x);
}
void splay(int x,int &rt){
int y=fa[x],z=fa[y];
while(x!=rt){
if(y!=rt){
if((x==ch[y][1])==(y==ch[z][1])) rotate(y,rt);
else rotate(x,rt);
}
rotate(x,rt);
y=fa[x];z=fa[y];
}
}
int insert(int x){
int u=rt,dir,v=0;
while(u){
++sze[v=u];
if(x==a[u]) return false;//在这个地方我就直接特判了相等的情况
else if(x<a[u]) dir=0,u=ch[u][0];
else dir=1,u=ch[u][1];
}
u=++tot;sze[tot]=1;a[tot]=x;
if(v) ch[v][dir]=u;fa[u]=v;
splay(u,rt);
return true;
}
int rk(int x){//将权值对应回编号
int u=rt;
while(u){
if(x==a[u]) return u;
else if(x<a[u]) u=ch[u][0];
else u=ch[u][1];
}
}
int find(int rt,int k){
//在以rt为根的子树中寻找第k大的数
//一定要时刻注意你所返回的是第k大的数在树中的位置下标,是一个编号!
//所以最后你要求的值应该是在a【】里
if(!k) return 0;
if(k>sze[rt]) return find(rt,sze[rt]);
int lsiz=sze[ch[rt][0]];
if(k==lsiz+1) return rt;
else if(k<=lsiz) return find(ch[rt][0],k);
else return find(ch[rt][1],k-lsiz-1);
}
int main(){
n=in;int sum=0,first=1,minn;
while(n--){
int x=in;
if(insert(x))
{
if(first){ sum=x;first=0;continue; }//我这里主要是用来判断第一天的营业额
int xx=rk(x);//找到x在平衡树中对应的节点号
splay(xx,rt);//将其伸展到根节点,其实这句话可以不要,因为在insert的时候已经splay了
int lmaxn=-3000009,rmaxn=3000009;
//最后一次就是这个地方出锅了,
//我rmaxn只定义到1000009,但至少应该保证初始的rmaxn-ai是大于1000009才行
if(ch[rt][0]) lmaxn=a[find(ch[rt][0],sze[ch[rt][0]])];//寻找左子树中最大的那个
if(ch[rt][1]) rmaxn=a[find(ch[rt][1],1)];//寻找右子树中最小的那个
sum+=min(a[xx]-lmaxn,rmaxn-a[xx]);
//下面是另外一种写法,会简单一些,直接不断的寻找左子树中最右边的,以及右子树中最左边的
/*int pre=ch[xx][0],suf=ch[xx][1];
if(pre) while(ch[pre][1]) pre=ch[pre][1];
if(suf) while(ch[suf][0]) suf=ch[suf][0];
if(!suf) minn=a[xx]-a[pre];
if(!pre) minn=a[suf]-a[xx];
if(suf&&pre) minn=min(a[xx]-a[pre],a[suf]-a[xx]);
sum+=minn;*/
}
}
printf("%d",sum);
return 0;
}
牢骚
数据结构的题最易出锅在:
1.手滑…… 敲代码的时候大意了,一些小错误
(但往往就是这些小小小小的errors,才会让人调疯。。。比如该打x,写成y???)
所以啊,一定要认真敲
2.脑抽…… 思维存在漏洞
(比如这道题,我一开始找的前驱和后继,居然就直接是ch [rt] [0] and ch [rt] [1]???后面模拟了好几遍才发现)
所以啊,想清楚再动手
话说这道题还可以写treap什么的,这个坑这几天应该就会填了,未完待续……