嘟嘟嘟




这几天开始搞平衡树了,\(splay\)理解起来感觉还行,然而代码看了半天才勉强看懂。

我这篇博客应该不算什么入门讲解,因为我觉得我讲不明白,所以只能算自己的学习笔记吧。




这道题就是有\(n\)个数,定义\(f_i = min{|a_i - a_j|}, 1 \leqslant j < i\),其中\(f_1 = a_1\)。然后求\(\sum{f_i}\)。

解法就是每添加一个数,查找这个数的前驱和后继,然后取更小的作为\(f_i\)。因此这需要平衡树维护。




那就先简单说一下\(splay\)。

众所周知,普通的\(bst\)最坏情况下会退化成一条链,导致操作变成了\(O(n)\)。因此就有很多大佬发明了各种平衡树来保持复杂度,\(splay\)就算一种。

\(splay\)通过旋转维护树的形态,使树看起来尽量平衡,从而保持每一个操作都是\(O(\log{n})\)。

每添加一个节点\(x\),都会把\(x\)转到根,从而避免树退化成链。

具体的旋转分为右旋\((zig)\)和左旋\((zag)\),然后不同的情况这两种旋转的顺序也会不一样。

第一种:



(感谢\(gg\)的图)

这个图已经表达的很明白了,如果\(x\)的父亲就是根节点的话,旋转一次即可,根据方向用\(zig\)或者\(zag\)。

第二种:



更多的是\(x\)的父亲不是根节点,这种情况是\(x\)和\(y\)在\(y\)的父亲\(z\)的同侧。这个时候要旋转两步,先把\(y\)旋转到\(z\)上,再把\(x\)旋转到\(y\)上。

第三种:



这个是\(y\)在\(z\)的一侧,而\(x\)在\(y\)的另一侧。这个时候应该先把\(x\)旋转到\(y\)上,在把\(x\)旋转到\(z\)上。至于为什么这个顺序,我也不是很清楚。但是反正这样转完后看起来确实很平衡。




理解起来好像还行。但是代码就不太友善了。我学的是把\(zig\)和\(zag\)放一块的写法,代码精简但比较难懂,估计还得消化一阵子吧。

我觉得理解起来的关键就是不要想是左还是右,而是用“这个儿子”和“另一个儿子”去表示。

void pushup(int now)
{
t[now].siz = t[t[now].ch[0]].siz + t[t[now].ch[1]].siz + t[now].cnt;
}
bool get(int x)
{
return t[t[x].fa].ch[1] == x;
}
void rotate(int x)
{
int y = t[x].fa, z = t[y].fa, k = get(x); //k:x是y的哪一个儿子
t[z].ch[t[z].ch[1] == y] = x; t[x].fa = z; //z的儿子从y换成x,就是
t[y].ch[k] = t[x].ch[k ^ 1]; t[t[y].ch[k]].fa = y;
//如果x是y的右儿子,那么y的右儿子现在变成了x的左儿子
t[x].ch[k ^ 1] = y; t[y].fa = x;
//然后x的右儿子现在是y
pushup(x); pushup(y);
}
void splay(int x, int s) //把x旋转到s,这题s传的都是0
{
//注意:树的根其实是0号节点的孩子,之所以传0,是为了写的方便
while(t[x].fa != s)
{
int y = t[x].fa, z = t[y].fa;
if(z != s) //这表示的是x还得再转两次,所以如果s就是根的话,转一次的情况就不直到怎么判了
{
//x, y同向先转y,否则先转x
//额外转一次
if((t[y].ch[0] == x) != (t[z].ch[0] == y)) rotate(x);
else rotate(y);
}
rotate(x); //在转一次
}
if(!s) root = x; //只有x要转到根的时候才更新根
}

\(splay\)函数我得再说一下。就是他不仅可以旋转到根,到任意一个祖先节点都行,但是这道题只用旋转到跟就行了。




然后是几个基本操作:

1.插入权值为\(x\)的元素

如果当前节点大于\(x\),就向左递归,否则向右,如果已经有了,就停止。

所以每一个节点有一个\(cnt\),记录权值为\(val\)的数有多少个。

如果没有,就要新建节点,具体看代码好了。

bool insert(int x)   //非递归版
{
int now = root, f = 0;
bool flg = 0; //flg是因为这道题需要,跟splay没有关系
while(now && t[now].val != x) f = now, now = t[now].ch[x > t[now].val];
if(now) t[now].cnt++, flg = 1; //这个数已经有了
else
{
now = ++ncnt;
if(f) t[f].ch[x > t[f].val] = now; //来个判断是为了整棵树还没有节点的情况
t[now].fa = f;
t[now].ch[0] = t[now].ch[1] = 0;
t[now].val = x;
t[now].siz = t[now].cnt = 1;
}
splay(now, 0);
return flg;
}

2.查找权值为\(x\)的元素

这个很简单,

代码里找到后顺便旋了上去,这个是为了找前驱后继用的。

void find(int x)
{
int now = root;
if(!now) return;
while(t[now].ch[x > t[now].val] && t[now].val != x) now = t[now].ch[x > t[now].val];
splay(now, 0);
}

3.找\(x\)的前驱,后继

我的做法是先找到\(x\),然后把他旋到根(此题保证\(x\)存在),这样的话找前驱就是走一步左儿子,再右儿子一直走到底。

后继同理:走一步右儿子,然后左儿子走到底。

int pre(int x)
{
find(x); //找到并把x旋到根
int now = t[root].ch[0];
while(t[now].ch[1]) now = t[now].ch[1];
return t[now].val;
}
int nxt(int x)
{
find(x);
int now = t[root].ch[1];
while(t[now].ch[0]) now = t[now].ch[0];
return t[now].val;
}



这道题只用到这几个操作,所以我就说这么多啦。
最后发一下完整代码
```c++
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define rg register
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 1e5 + 5;
inline ll read()
{
ll ans = 0;
char ch = getchar(), last = ' ';
while(!isdigit(ch)) last = ch, ch = getchar();
while(isdigit(ch)) ans = (ans = 10) write(x / 10);
putchar(x % 10 + '0');
}

int n, ans = 0;

struct Tree

{

int ch[2], fa;

int siz, cnt, val;

}t[maxn << 1];

int root = 0, ncnt = 0;

void pushup(int now)

{

t[now].siz = t[t[now].ch[0]].siz + t[t[now].ch[1]].siz + t[now].cnt;

}

bool get(int x)

{

return t[t[x].fa].ch[1] == x;

}

void rotate(int x)

{

int y = t[x].fa, z = t[y].fa, k = get(x);

t[z].ch[t[z].ch[1] == y] = x; t[x].fa = z;

t[y].ch[k] = t[x].ch[k ^ 1]; t[t[y].ch[k]].fa = y;

t[x].ch[k ^ 1] = y; t[y].fa = x;

pushup(x); pushup(y);

}

void splay(int x, int s)

{

while(t[x].fa != s)

{

int y = t[x].fa, z = t[y].fa;

if(z != s)

{

if((t[y].ch[0] == x) != (t[z].ch[0] == y)) rotate(x);

else rotate(y);

}

rotate(x);

}

if(!s) root = x;

}

bool insert(int x)

{

int now = root, f = 0;

bool flg = 0;

while(now && t[now].val != x) f = now, now = t[now].ch[x > t[now].val];

if(now) t[now].cnt++, flg = 1;

else

{

now = ++ncnt;

if(f) t[f].ch[x > t[f].val] = now;

t[now].fa = f;

t[now].ch[0] = t[now].ch[1] = 0;

t[now].val = x;

t[now].siz = t[now].cnt = 1;

}

splay(now, 0);

return flg;

}

void find(int x)

{

int now = root;

if(!now) return;

while(t[now].ch[x > t[now].val] && t[now].val != x) now = t[now].ch[x > t[now].val];

splay(now, 0);

}

int pre(int x)

{

find(x);

int now = t[root].ch[0];

while(t[now].ch[1]) now = t[now].ch[1];

return t[now].val;

}

int nxt(int x)

{

find(x);

int now = t[root].ch[1];

while(t[now].ch[0]) now = t[now].ch[0];

return t[now].val;

}

int main()

{

n = read();

insert(INF); insert(-INF);

int x = read(); ans += x; insert(x);

for(int i = 2; i <= n; ++i)

{

int x = read();

if(insert(x)) continue;

int a = pre(x), b = nxt(x);

ans += min(x - a, b - x);

}

write(ans), enter;

return 0;

}

[HNOI2002]营业额统计(splay基础)的更多相关文章

  1. BZOJ1588 HNOI2002 营业额统计 [Splay入门题]

    [HNOI2002]营业额统计 Time Limit: 5 Sec  Memory Limit: 162 MBSubmit: 4128  Solved: 1305 Description 营业额统计 ...

  2. 1588: [HNOI2002]营业额统计 (splay tree)

    1588: [HNOI2002]营业额统计 Time Limit: 5 Sec  Memory Limit: 162 MBSubmit: 5783  Solved: 1859[Submit][Stat ...

  3. [HNOI2002]营业额统计 Splay tree入门题

    题目连接:http://www.lydsy.com/JudgeOnline/problem.php?id=1588 1588: [HNOI2002]营业额统计 Time Limit: 5 Sec   ...

  4. BZOJ1588 [HNOI2002]营业额统计 splay模板

    1588: [HNOI2002]营业额统计 Time Limit: 5 Sec  Memory Limit: 162 MB Submit: 16189  Solved: 6482 [Submit][S ...

  5. bzoj1588: [HNOI2002]营业额统计(splay)

    1588: [HNOI2002]营业额统计 题目:传送门 题解: 复习splay所以来刷个水... 题目描述不是特别清楚:应该是找第i天以前一个最小的营业额和第i天做差的最小值作为第i天的最小波动值 ...

  6. Bzoj 1588: [HNOI2002]营业额统计(splay)

    1588: [HNOI2002]营业额统计 Time Limit: 5 Sec Memory Limit: 162 MB Description 营业额统计 Tiger最近被公司升任为营业部经理,他上 ...

  7. 洛谷P2234 [HNOI2002] 营业额统计 [splay]

    题目传送门 营业额统计 题目描述 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司的账本,账本上记录了公司成立以来每天 ...

  8. HNOI2002 营业额统计 [Splay]

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

  9. bzoj1588: [HNOI2002]营业额统计 splay瞎写

    最近各种瞎写数论题,感觉需要回顾一下数据结构 写一发splay冷静一下(手速过慢,以后要多练练) 用splay是最直接的方法,但我感觉离散一波应该可以做出来(没仔细想过) 现在没有很追求代码优美,感觉 ...

  10. 洛谷.2234.[HNOI2002]营业额统计(Splay)

    题目链接 //模板吧 #include<cstdio> #include<cctype> #include<algorithm> using namespace s ...

随机推荐

  1. XML序列化与REST WCF Data Contract匹配时遇到的2个问题

    问题一: XML序列化与RESTful WCF Data Contract不能匹配,无法传递类的值. 现象: 给类加上[Serializable]Attribute,可以成功序列化,但是WCF Ser ...

  2. C Traps:优先级常见错误

    逻辑与关系运算符 if (flags & FLAG != 0) {...} 这类错误以前也犯过,因为!=的优先级比&要高所以实际上是这样的 if (flags & (FLAG ...

  3. 由Leetcode详解算法 之 动态规划(DP)

    因为最近一段时间接触了一些Leetcode上的题目,发现许多题目的解题思路相似,从中其实可以了解某类算法的一些应用场景. 这个随笔系列就是我尝试的分析总结,希望也能给大家一些启发. 动态规划的基本概念 ...

  4. HDU4418 Time travel(期望dp 高斯消元)

    题意 题目链接 Sol mdzz这题真的太恶心了.. 首先不难看出这就是个高斯消元解方程的板子题 \(f[x] = \sum_{i = 1}^n f[to(x + i)] * p[i] + ave\) ...

  5. 2017年5月22日 HTML基础知识(一)

    一.Html 结构 1.1.HTML基本文档格式—<html> 标记 —<html>文档的头部好和主体内容 </html>  根标记 —<head> 文 ...

  6. js中闭包和作用域

    将这方面很好的一系列文章:http://www.cnblogs.com/wangfupeng1988/p/3977987.html

  7. <VS2010>混合模式程序集是针对“v2.0”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集

    在把以前写的代码生成工具从原来的.NET3.5升级到.NET4.0时,将程序集都更新后,一运行程序在一处方法调用时报出了一个异常: 混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有 ...

  8. MyBatis -01- 初识 MyBatis + MyBatis 环境搭建

    MyBatis -01- 初识 MyBatis + MyBatis 环境搭建 MyBatis 本是 apache 的一个开源项目 iBatis(iBATIS = "internet" ...

  9. 基于 Docker 的现代软件供应链

    [编者按]本文作者为 Marc Holmes,主要介绍一项关于现代软件供应链的调查结果.本文系国内 ITOM 管理平台 OneAPM 编译呈现,以下为正文. 3 月初,为了了解软件供应链的现状以及 D ...

  10. 【Kendo UI系列开发使用笔记】01-简单介绍

    ps:接触telerik出品的kendo ui系列已经快有一年了,使用过程中也在不断踩坑填坑.这套UI用起来还是非常爽的,尤其asp.net mvc版的配合lambda表达式来配置参数非常流畅.这次对 ...