营业额统计 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。 Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况: 该天的最小波动值 当最小波动值越大时,就说明营业情况越不稳定。 而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助Tiger来计算这一个值。 第一天的最小波动值为第一天的营业额。 输入输出要求
Input第一行为正整数 ,表示该公司从成立一直到现在的天数,接下来的n行每行有一个整数(有可能有负数) ,表示第i
天公司的营业额。
天数n<=32767,
每天的营业额ai <= 1,000,000。
最后结果T<=2^31
Output
输出文件仅有一个正整数,即Sigma(每天最小的波动值) 。结果小于2^31 。
Sample Input65
1
2
5
4
6
Sample Output12
Hint
结果说明:5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12
题解:
用数据结构平衡树
这个就是找出来新插入的数的前驱那个数和后继那个数,然后取那个距离新插入的数近的那个数就可以了
代码:
/* 注意: 1、看平衡树之前你要注意,对于1 3 5 3 2这一组数据。sz的值是4,因为sz保存的是节点种类 为什么要这样,因为sz涉及到要为几个点开空间 2、sizes[x]保存的是以x为树根的子树上节点数量,比如x这颗子树所有节点为1,2,1.那么它的sizes[x]=3 而且实际上不会有两个1放在树上。而是给1的权值数组cnt[1]加1 3、对于1 3 5 3 2这一组数据。sz的值是4。那么1对应位置就是1,3对应位置就是2,5对应位置就是3,2对应位置就是4 之后他们所对应的位置都不会改变 在平衡树旋转的过程中只是每一个点的儿子节点ch[x][0]和ch[x][1]里面保存的值在改变 */ #include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> using namespace std; const int maxn=1e5+10; typedef long long ll; ll f[maxn],cnt[maxn],ch[maxn][2],sizes[maxn],key[maxn],sz,rt; /* f[i]:i节点的父节点,cnt[i]每个点出现的次数,ch[i][0/1]:0表示左孩子, 1表示右孩子, size[i]表示以i为根节点的子树的节点个数 key[i]表示点i代表的数的值;sz为整棵树的节点种类数,rt表示根节点 */ void clears(ll x) //删除x点信息 { f[x]=cnt[x]=ch[x][0]=ch[x][1]=sizes[x]=key[x]=0; } bool get(ll x) //判断x是父节点的左孩子还是右孩子 { return ch[f[x]][1]==x; //返回1就是右孩子,返回0就是左孩子 } void pushup(ll x) //重新计算一下x这棵子树的节点数量 { if(x) { sizes[x]=cnt[x]; if(ch[x][0]) sizes[x]+=sizes[ch[x][0]]; if(ch[x][1]) sizes[x]+=sizes[ch[x][1]]; } } void rotates(ll x) //将x移动到他父亲的位置,并且保证树依旧平衡 { ll fx=f[x],ffx=f[fx],which=get(x); //x点父亲,要接受x的儿子。而且x与x父亲身份交换 ch[fx][which]=ch[x][which^1]; f[ch[fx][which]]=fx; ch[x][which^1]=fx; f[fx]=x; f[x]=ffx; if(ffx) ch[ffx][ch[ffx][1]==fx]=x; pushup(fx); pushup(x); } void splay(ll x) //将x移动到数根节点的位置,并且保证树依旧平衡 { for(ll fx; fx=f[x]; rotates(x)) { if(f[fx]) { rotates((get(x)==get(fx))?fx:x); //如果祖父三代连城一条线,就要从祖父哪里rotate //至于为什么要这样做才能最快……可以去看看Dr.Tarjan的论文 } } rt=x; } /* 将x这个值插入到平衡树上面 如果这个值在树上存在过,那就sz不再加1,更新一下权值即可 如果这个值在树上不存在,那就sz加1,再更新一下权值 sz是书上节点种类数 sizes[x]是x这棵子树上有多少节点 */ void inserts(ll x) { if(rt==0) { sz++; //说明平衡数顶点是从1开始的 key[sz]=x; rt=sz; cnt[sz]=sizes[sz]=1; f[sz]=ch[sz][0]=ch[sz][1]=0; return; } ll now=rt,fx=0; while(1) { if(x==key[now]) { cnt[now]++; pushup(now); pushup(fx); splay(now); //splay的过程会rotates now点的所有祖先节点,这个时候它们所有子树权值也更新了 return; } fx=now; now=ch[now][key[now]<x]; if(now==0) { sz++; sizes[sz]=cnt[sz]=1; ch[sz][0]=ch[sz][1]=0; ch[fx][x>key[fx]]=sz; //二叉查找树特性”左大右小“ f[sz]=fx; key[sz]=x; pushup(fx); splay(sz); return ; } } } /* 有人问: qwq很想知道为什么find操作也要splay操作呢?如果del要用的话直接splay(x)是不是就可以了 原博客答: 呃不不不这个貌似不是随便splay以下就可以的 首先find之后的splay就是将找到的这个点转到根, 当然你不加这个应该是也可以,只不过这道题加上的话对于这一堆操作来说比较方便,不过一般来说转一转splay的 平衡性会好一点(当然也不要转得太多了就tle了...) 但是del之前直接splay(x)要视情况而定,关键在于分清楚 “点的编号”和“点的权值”这两个概念。如果你已经知道了该转的点的编号,当然可以直接splay(x),但是如果你只 知道应该splay的点的权值,你需要在树里find到这个权值的点的编号,然后再splay 其实最后splay写起来都是非 常灵活的,而且有可能一个点带若干个权之类的。对于初学者的建议就是先把一些最简单的情况搞清楚,比如说一 个编号一个权的这种,然后慢慢地多做题就能运用得非常熟练了。最好的方法就是多画画树自己转一转,对之后 复杂题目的调试也非常有益 我说: 我在洛谷上得模板题上交了一下rnk里面不带splay(now)的,一共12个样例,就对了两个样例。错了一个样例,其他全TLE 我解释: 为什么作者解释可以删去,但是删过之后还错了。因为它的代码中函数之前是相互联系的 就比如它调用rnk(x)函数之后就已经认为x为平衡树树根,然后直接对它进行下一步操作(这个假设在del函数里面) 如果你光删了rnk(x)里面的splay(),你肯定还要改其他地方代码。。。。。。 */ ll rnk(ll x) //查询x的排名 { ll now=rt,ans=0; while(1) { if(x<key[now]) now=ch[now][0]; else { ans+=sizes[ch[now][0]]; if(x==key[now]) { splay(now); //这个splay是为了后面函数的调用提供前提条件 //就比如pre函数的前提条件就是x(x是我们要求谁的前驱,那个谁就是x)已经在平衡树树根 return ans+1; } ans+=cnt[now]; //cnt代表now这个位置值(key[now])出现了几次 now=ch[now][1]; } } } ll kth(ll x) { ll now=rt; while(1) { if(ch[now][0] && x<=sizes[ch[now][0]]) { //满足这个条件就说明它在左子树上 now=ch[now][0]; } else { ll temp=sizes[ch[now][0]]+cnt[now]; if(x<=temp) //这个temp是now左子树权值和now节点权值之和 return key[now]; //进到这个判断里面说明他不在左子树又不在右子树,那就是now节点了 x-=temp; now=ch[now][1]; } } } ll pre()//由于进行splay后,x已经到了根节点的位置 { //求x的前驱其实就是求x的左子树的最右边的一个结点 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在 //x的左子树的最右边的一个结点 ll now=ch[rt][0]; if(now==0) return 0; while(ch[now][1]) now=ch[now][1]; return now; } ll next() { //求后继是求x的右子树的最左边一个结点 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在 //x的右子树的最左边一个结点 ll now=ch[rt][1]; if(now==0) return 0; while(ch[now][0]) now=ch[now][0]; return now; } /* 删除操作是最后一个稍微有点麻烦的操作。 step 1:随便find一下x。目的是:将x旋转到根。 step 2:那么现在x就是根了。如果cnt[root]>1,即不只有一个x的话,直接-1返回。 step 3:如果root并没有孩子,就说名树上只有一个x而已,直接clear返回。 step 4:如果root只有左儿子或者右儿子,那么直接clear root,然后把唯一的儿子当作根就可以了(f赋0,root赋为唯一的儿子) 剩下的就是它有两个儿子的情况。 step 5:我们找到新根,也就是x的前驱(x左子树最大的一个点),将它旋转到根。然后将原来x的右子树接到新根的 右子树上(注意这个操作需要改变父子关系)。这实际上就把x删除了。不要忘了update新根。 */ void del(ll x) { rnk(x); if(cnt[rt]>1)//如果这个位置权值大于1,那就不用删除这个点 { cnt[rt]--; pushup(rt); return; } if(!ch[rt][0] && !ch[rt][1]) //这个就代表平衡树只有一个节点 { clears(rt); rt=0; return; } if(!ch[rt][0]) //只有左儿子,树根只有左儿子那就把树根直接删了就行 { //然后左儿子这棵子树变成新的平衡树 ll frt=rt; rt=ch[rt][1]; f[rt]=0; clears(frt); return; } else if(!ch[rt][1]) //只有右儿子,和上面差不多 { ll frt=rt; rt=ch[rt][0]; f[rt]=0; clears(frt); return; } ll frt=rt; ll leftbig=pre(); splay(leftbig); //让前驱做新根 ch[rt][1]=ch[frt][1]; //这个frt指向的还是之前的根节点 /* 看着一点的时候就会发现,数在数组里面的位置一直没有改变,平衡树旋转改变的是根节点儿子数组ch[x][]指向的值 */ f[ch[frt][1]]=rt; clears(frt); pushup(rt); } int main() { ll n,ans=0; scanf("%lld",&n); for (ll i=1; i<=n; i++) { ll k,temp1,temp2,minn; scanf("%lld",&k); inserts(k); if(i>1) { if(cnt[rt]==1) { temp1=pre(); temp2=next(); if(temp1 && temp2) { minn=min(k-key[temp1],key[temp2]-k); } else if(temp1) { minn=k-key[temp1]; } else if(temp2) { minn=key[temp2]-k; } //printf("%lld %lld %lld %lld %lld ",temp1,temp2,key[temp1],key[temp2],minn); ans=ans+minn; } } else { ans+=k; } } printf("%lld ",ans); return 0; }