题意:给出一组数,设定每个数与其前面的数之间最小相距值的绝对值为最小波动值(第一个数的最小波动值为其本身)。求所有最小波动值之和。
Splay入门题。此题仅包含插入,旋转和伸展操作。
可先参考Crash前辈的论文《运用伸展树解决数列维护问题》。
首先是旋转操作。
和一般的平衡树旋转操作类似,分为左旋和右旋。
将左儿子旋转至根,需要执行右旋操作。
旋转前:(x为左儿子,y为根节点) 旋转后:
将右儿子旋转至根节点,需要执行左旋操作。
旋转前:(y为右儿子,x为根节点) 旋转后:
显然,执行x旋操作,就是将!x儿子旋转至根节点,并把儿子的x子树替换给原根节点。
如果希望是根节点的儿子的儿子被旋转至根节点,则需要执行一字形操作或是之字形操作。
一字形:
例如右旋一字形,目的是将浅蓝色节点旋转至树根:首先将浅紫色节点旋转至根(右旋),然后将浅蓝色节点旋转至根(右旋)。
之字形:(仅显示一种,另一种对称)
(举例图上例子)
首先将绿色节点旋转至其父亲节点位置(右旋);然后再旋转一次绿色节点(左旋)。
(两次都是旋转绿色节点)
接下来是伸展操作:
伸展操作与旋转操作联系十分紧密,伸展操作的目的,是将某节点x,通过一层层旋转,变成另外一个节点f的子节点。
假令x的父亲为y,y的父亲为z。
如果y的父亲已经是f,则只需一次单旋转;
如果不是则继续查询x,y和z之间的关系,以此来决定是一字形旋转,还是之字形旋转,然后将x挪至z;
继续前面的操作,直至x的父亲变为f。
接下来是插入操作和查询操作。
对于本题,每个数字都需要查询当前时刻,在数值上与其最相邻的数是什么。
此处则是巧妙运用Splay树性质的地方了:中序遍历一棵splay树,即是这个数组的顺序。
此题中,Splay树维护着一个按大小排序的数组。
显然最方便的操作是:插入一个数时,即将这个数旋转至根。这样查询遍十分方便。
深搜(左子节点小于父亲,右子节点大于父亲,不保存相同的值)当查询至某节点的值正好等于插入值时,仅需伸展一次(很大的可能性可以使得树更加平衡);
当走至树的叶子节点也没有找到该插入值时,建立一个新点,插入该叶子节点的左子节点(值小于父亲节点)或右子节点(值大于父亲节点),并将插入的点旋转至树根处。
接下来按照中序遍历的性质即可,左相邻值是左子树的最右点,右相邻值是右子树的最左点。
#include <algorithm> #include <iostream> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <math.h> using namespace std; #define MAXN 100010 #define INF 0x3f3f3f3f struct SplayTree { int fa[MAXN], ch[MAXN][2], val[MAXN]; int cnt, root; void New(int &t, int f, int v) { t = ++cnt; fa[t] = f, val[t] = v; ch[t][0] = ch[t][0] = 0; } //旋转操作,c = 0表示左旋,c = 1表示右旋 void Rotate(int x, int c) { int y = fa[x]; int z = fa[y]; ch[y][!c] = ch[x][c]; if(ch[x][c]) fa[ch[x][c]] = y; fa[x] = fa[y]; if(fa[y]) { if(ch[z][0] == y) ch[z][0] = x; else ch[z][1] = x; } ch[x][c] = y, fa[y] = x; if(y == root) root = x; } //Splay操作,表示把结点x转到结点f的下面 void Splay(int x, int f) { while(fa[x] != f) { int y = fa[x]; int z = fa[y]; //父节点即为f,执行单旋转 if(fa[y] == f) { if(ch[y][0] == x) Rotate(x, 1); else Rotate(x, 0); } else { if(ch[z][0] == y) { if(ch[y][0] == x) Rotate(y, 1), Rotate(x, 1); //一字型旋转 else Rotate(x, 0), Rotate(x, 1); //之字形旋转 } else { if(ch[y][1] == x) Rotate(y, 0), Rotate(x, 0); //一字形旋转 else Rotate(x, 1), Rotate(x, 0); //之字形旋转 } } } if(f == 0) root = x; } int l() { int t = root; if(ch[t][0] == 0) return INF; t = ch[t][0]; while(ch[t][1]) t = ch[t][1]; return val[t]; } int r() { int t = root; if(ch[t][1] == 0) return INF; t = ch[t][1]; while(ch[t][0]) t = ch[t][0]; return val[t]; } int Insert(int k) { int r = root, c; while(true) { if(val[r] == k) { Splay(r, 0); return 0; } c = k < val[r] ? 0 : 1; if(ch[r][c] == 0) break; r = ch[r][c]; } c = k < val[r] ? 0 : 1; New(ch[r][c], r, k); Splay(ch[r][c], 0); return 1; } }splay; int main() { int n; while(~scanf("%d", &n)) { int ans = 0; splay.root = splay.cnt = 0; for(int i = 0; i < n; i++) { int x = 0; scanf("%d", &x); if(!i) { ans += x; splay.New(splay.root, 0, x); } else { int t = splay.Insert(x); if(!t) continue; int l = splay.l(); int r = splay.r(); ans += min(abs(l - x), abs(r - x)); } } printf("%d\n", ans); } return 0; }