P2234 [HNOI2002] 营业额统计
题目翻译:
给定 \(n\) 个数,每一个数都要统计其最小波动值,波动值的定义是当天银收额和之前某次的营收额的差的绝对值,而要求每一天最小波动值的和(第一天波动值为当天营收额)
思路:
分析题目可以发现,最小波动值就是当天营收额与之前小于它的最大营收额的差的绝对值或之前大于它的最小营收额的差的绝对值。即当天的营收额的前驱和后继。想到这里就会想到二叉搜索树,为了防止树太高被卡,于是用平衡树来维护,接下来就是直接套模板即可。
注意: 要提前在树中插入一个极大值和极小值。
完整代码:
#include<bits/stdc++.h>
#define lc(x) tr[(x)].s[0]
#define rc(x) tr[(x)].s[1]
#define fa(x) tr[(x)].fa
using namespace std;
const int N=32800;
const int INF=0x3f3f3f3f;
struct tree{
int s[2],key,fa,siz;
tree(){s[0]=s[1]=fa=key=siz=0;}
}tr[N];
int rt,cnt;
void clear(int x){
lc(x)=rc(x)=fa(x)=tr[x].key=tr[x].siz=0;
}
void pushup(int x){
tr[x].siz=tr[lc(x)].siz+tr[rc(x)].siz;
}
bool get(int x){
return x==rc(fa(x));
}
int newnode(int key){
tr[++cnt].key=key;
tr[cnt].siz=1;
return cnt;
}
void rotate(int x){
int y=fa(x),z=fa(y),c=get(x);
if(tr[x].s[c^1]){
fa(tr[x].s[c^1])=y;
}
tr[y].s[c]=tr[x].s[c^1];
tr[x].s[c^1]=y;
fa(y)=x;
fa(x)=z;
if(z)tr[z].s[y==tr[z].s[1]]=x;
pushup(y);
pushup(x);
}
void splay(int x){
for(int f=fa(x);f=fa(x),f;rotate(x)){
if(fa(f))rotate(get(x)==get(f)?f:x);
}
rt=x;
}
void ins(int key){
int now=rt,f=0;
while(now){
f=now;
now=tr[now].s[key>tr[now].key];
}
now=newnode(key);
fa(now)=f;
tr[f].s[key>tr[f].key]=now;
splay(now);
}
int pre(int key){
int now=rt,f=0,ans=0;
while(now){
f=now;
if(tr[now].key>=key){
now=lc(now);
}
else{
ans=tr[now].key;
now=rc(now);
}
}
splay(f);
return ans;
}
int nxt(int key){
int now=rt,f=0,ans=0;
while(now){
f=now;
if(tr[now].key<=key){
now=rc(now);
}
else{
ans=tr[now].key;
now=lc(now);
}
}
splay(f);
return ans;
}
map<int,bool>vis;
signed main(){
long long n,ans=0;
scanf("%lld",&n);
ins(INF),ins(-INF);
int val;
scanf("%d",&val);
ins(val);
ans+=val;
vis[val]=true;
for(int i=2;i<=n;i++){
int val;
scanf("%d",&val);
if(vis[val]){
continue;
}
vis[val]=true;
ins(val);
ans+=min(abs(val-pre(val)),abs(val-nxt(val)));
}
printf("%lld\n",ans);
}