[HNOI2002]营业额统计_Treap
[HNOI2002]营业额统计
题目大意:给你一串n数序列,对于每一个刚输入的数a,找到一个前面的数k,使得|a-k|最小。
注释:$n<=32767,ai<=10^6$。
想法:刚学Treap。这道算Treap的练习题里吧,对于新手来讲还是挺有意义的。首先,我们先来讲一讲Treap是个什么东西。
在这之前,我们声明:
struct Node
{
int lson,rson;//lson和rson分别表示左右儿子的编号
int size,num;//size表示以当前节点为根节点的子树的节点数,num表示在Treap中有多少是和当前权值重复的
int val,rnd;//val表示Tree的权值,rnd表示Heap的随机数据
}
void push(int k)//push代表将子树的信息更新至根节点
{
a[k].size=a[k].num+a[a[k].lson].size+a[a[k].rson].size;
}
Treap,是二叉搜索树(Tree)和堆(Heap)的合体,读音也是这两个东西捏在一起的(懵-)。我们来看一下这东西是怎么实现的。
Treap是一种二叉搜索树,但也是一种平衡树。我们显然知道,对于二叉搜索树来讲,如果插入的顺序是直接升序或者降序,二叉搜索树就会退化成一条链,查找和修改还有删除的时间复杂度都会从原本的期望logn滚粗会O(n)。这就很蛋疼,而且卡二叉搜索树的题有不少,那么我们可以用一种什么样的办法来使得二叉搜索树趋近于平衡的呢?这是一个问题。然后我们再来看另一个问题。
关于Heap来讲,升序或者降序对于堆的伤害显然是... ...没有的。我们尝试用Heap将二叉搜索树进行一定的改造。之前的二叉搜索树的节点维护的只是权值,现在,我们让它在多一个随机的rand。这样的好处是什么?就是可以用这个随机的rand来使得存在一个树,满足这个树对于原本二叉搜索树的权值来讲是满足二叉搜索树的,对于rand来讲是满足heap的(这里是大根堆还是小根堆是无所谓的)。如何来呈现一颗这样的树呢?我们通过旋转Treap的核心操作——旋转。如何进行旋转?首先,我们最主要的目的就在保证二叉搜索树的前提下完成对堆的构建。那么,我们来考虑在如何不改变原有二叉搜索树的前提下旋转堆。首先,二叉搜索树的大小排序是由中序遍历决定的,而堆的大小排序是由深度决定的。我们可以通过这样的操作:
void lturn(int &k)//向左旋转
{
int t=a[k].rson;
a[k].rson=a[t].lson;
a[t].lson=k;
push(k);push(t);k=t;
}
没错,就是这样可以达到我们想要的效果。至于图解...菜逼的我并不会怎么把图粘上来,所以自己在纸上推演即可。然后,附上一些版子,知道了Treap的旋转之后就很好理解了。说一说插入,在插入节点时,先递归处理插入符合二叉搜索树的权值,然后取rand,通过递归旋转达到同时满足Tree和Heap的神奇的树即可。
void update(int &k,int temp)
{
if(!k)
{
k=++tot;
a[k].num=a[k].size=1;
a[k].val=temp;
a[k].rnd=rand();
return ;
}
a[k].size++;
if(temp==a[k].val) a[k].num++;
else if(temp<a[k].val)
{
update(a[k].lson,temp);
if(a[a[k].lson].rnd<a[k].rnd) rturn(k);
}
else
{
update(a[k].rson,temp);
if(a[a[k].rson].rnd<a[k].rnd) lturn(k);
}
}
删除和插入的原理是大致相同的,不在赘述。向说一说找前驱和后继的事情。我是照着lijinnn的版子敲的,所以找前驱和后继是递归的,EdwardFrog说可以不递归,推荐看一下,在此贴递归版子前驱
void ask_before(int k,int temp)
{
if(!k) return ;
if(temp>a[k].val)
{
befor=max(befor,a[k].val);
ask_before(a[k].rson,temp);
}
else ask_before(a[k].lson,temp);
}
然后,说一下这道题。由于求的是最小波动,我们考虑什么样的数在Treap上和当前输入的数的波动是最小的?显然,是前驱或者是后继即可。所以,我们只需要找到对应的前驱、后继,然后对于波动的绝对值取min即可。
最后,附上丑陋的代码... ...
#include <iostream>
#include <cstdio>
#include <algorithm>//rand的时候会用到
using namespace std;
int befor,aftr;
int tot;
int root;
// int v[1000010];
int AbS(int x)//建议手写abs
{
if(x<0) return -x;
else return x;
}
struct Node
{
int size;
int lson,rson;
int num,val,rnd;
}a[50010];
void push(int k)//更新节点信息
{
a[k].size=a[k].num+a[a[k].lson].size+a[a[k].rson].size;
}
void lturn(int &k)//向左旋转
{
int t=a[k].rson;
a[k].rson=a[t].lson;
a[t].lson=k;
push(k);push(t);k=t;
}
void rturn(int &k)//向右旋转
{
int t=a[k].lson;
a[k].lson=a[t].rson;
a[t].rson=k;
push(k);push(t);k=t;
}
void update(int &k,int temp)//在以k为根节点的树内插入一个权值为temp的节点
{
if(!k)//如果已经到达了叶子结点,就新增节点
{
k=++tot;
a[k].num=a[k].size=1;
a[k].val=temp;
a[k].rnd=rand();
}
a[k].size++;//显然,在以k为根节点的子树中添加节点,k的size必须+1
if(temp==a[k].val) a[k].num++;
else if(temp<a[k].val)//左右递归处理即可
{
update(a[k].lson,temp);
if(a[a[k].lson].rnd<a[k].rnd) rturn(k);
}
else
{
update(a[k].rson,temp);
if(a[a[k].rson].rnd<a[k].rnd) lturn(k);
}
}
// int ask_rank(int k,int temp)//只是一个版子
// {
// if(!k) return 0;
// if(a[k].val==temp) return a[a[k].lson].size+1;
// else if(temp>a[k].val)
// return a[a[k].lson].size+a[k].num+ask_rank(a[k].rson,temp);
// else return ask_rank(a[k].lson,temp);
// }
void ask_before(int k,int temp)//前驱
{
if(!k) return;
if(temp>=a[k].val)
{
befor=max(befor,a[k].val);
ask_before(a[k].rson,temp);
}
else ask_before(a[k].lson,temp);
}
void ask_after(int k,int temp)//后继
{
if(!k) return;
if(temp<=a[k].val)
{
aftr=min(aftr,a[k].val);
ask_after(a[k].lson,temp);
}
else ask_after(a[k].rson,temp);
}
int main()
{
int n;
scanf("%d",&n);
int All=0,ans;
int x;
scanf("%d",&x);
All=x;
update(root,x);
// printf("root:%d\n",root);
for(int i=2;i<=n;i++)
{
scanf("%d",&x);
ans=0x7f7f7f7f;
befor=0xefefefef;aftr=0x7f7f7f7f;//防止在取前驱的时候使得没有比当前节点大或小时befor和aftr为0
ask_after(root,x);
ask_before(root,x);
ans=min(ans,AbS(befor-x));
ans=min(ans,AbS(aftr-x));
// printf("befor:%d aftr:%d ans:%d\n",befor,aftr,ans);
All+=ans;
update(root,x);
}
printf("%d\n",All);
}
小结:学到了一个超级强大的数据结构啊!!!。
错误:1.之前的做法是取rank,我们要明白,节点的编号对于Treap来讲,只有在对整个Treap进行修改时才有用,对于使用Treap来讲用处是极其小的。
2.update的!k并不是return,我们要明白update的最核心的一句话其实是第一句。只有在!k的时候才有可能对于整个Treap进行形式上的增加节点的操作(有可能之前有重复)。
[HNOI2002]营业额统计_Treap的更多相关文章
- BZOJ1588: [HNOI2002]营业额统计[BST]
1588: [HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 14151 Solved: 5366[Submit][Sta ...
- BZOJ 1588: [HNOI2002]营业额统计
1588: [HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 14396 Solved: 5521[Submit][Sta ...
- BZOJ 1588: [HNOI2002]营业额统计 双向链表 / splay / treap
1588: [HNOI2002]营业额统计 Description 营业额统计 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger ...
- BZOJ1588 HNOI2002 营业额统计 [Splay入门题]
[HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 4128 Solved: 1305 Description 营业额统计 ...
- bzoj 1588: [HNOI2002]营业额统计 treap
1588: [HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 13902 Solved: 5225[Submit][Sta ...
- 数据结构:(平衡树,链表)BZOJ 1588[HNOI2002]营业额统计
1588: [HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 12173 Solved: 4354[Submit][Sta ...
- bzoj1588 [HNOI2002]营业额统计(Treap)
1588: [HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 11485 Solved: 4062[Submit][Sta ...
- 1588: [HNOI2002]营业额统计
1588: [HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 9203 Solved: 3097[Submit][Stat ...
- 【链表】BZOJ1588: [HNOI2002]营业额统计
1588: [HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 17555 Solved: 7179[Submit][Sta ...
随机推荐
- 错误代码: 1327 Undeclared variable: p_film_count
1.错误描述 1 queries executed, 0 success, 1 errors, 0 warnings 查询:SELECT FOUND_ROWS() INTO p_film_count ...
- freemarker定义一个连续的序列
freemarker定义一个连续的序列 1.简易说明 定义一个连续的序列,并打印出序列中的元素 2.实现源码 <#--freemarker定义了一个连续的序列--> <#assign ...
- 安装Android的SDK
安装Android的SDK 1.首先,下载installer_r23.0.2-windows.exe 2.双击"installer_r23.0.2-windows.exe",进入A ...
- EF Core下利用Mysql进行数据存储在并发访问下的数据同步问题
小故事 在开始讲这篇文章之前,我们来说一个小故事,纯素虚构(真实的存钱逻辑并非如此) 小刘发工资后,赶忙拿着现金去银行,准备把钱存起来,而与此同时,小刘的老婆刘嫂知道小刘的品性,知道他发工资的日子,也 ...
- Complete the Word CodeForces - 716B
ZS the Coder loves to read the dictionary. He thinks that a word is nice if there exists asubstring ...
- 论文笔记(6):Weakly-and Semi-Supervised Learning of a Deep Convolutional Network for Semantic Image Segmentation
这篇文章的主要贡献点在于: 1.实验证明仅仅利用图像整体的弱标签很难训练出很好的分割模型: 2.可以利用bounding box来进行训练,并且得到了较好的结果,这样可以代替用pixel-level训 ...
- 论文笔记(1):From Image-level to Pixel-level Labeling with Convolutional Networks
文章采用了多实例学习(MIL)机制构建图像标签同像素语义的关联 . 该方法的训练样本包含了70 万张来自ImageNet的图片,但其语义分割的性能很大程度上依赖于复杂的后处理过程,主要包括图像级语义的 ...
- ES6学习总结二(数组的四个方法,字符串)
数组 1 map 映射 一个对一个 如:分数数组[34,56,78,99]映射为[不及格,不及格,及格,及格]; 等级数组[23,56,89]映射为 [ {name:'lmx',level:1,rol ...
- IE浏览器右键菜单插件开发(上篇)——自定义一个IE右键菜单项
要做一个IE右键浏览器插件,得3步走. 第一,在IE右键菜单上添加自定义菜单名称,是通过注册表实现的,如下: string regkey = @"Software\Microsoft\Int ...
- Java 对IP请求进行限流.
高并发系统下, 有三把利器 缓存 降级 限流. 缓存: 将常用数据缓存起来, 减少数据库或者磁盘IO 降级: 保护核心系统, 降低非核心业务请求响应 限流: 在某一个时间窗口内对请求进行限速, 保护系 ...