[Note]Splay学习笔记
开个坑记录一下学习Splay的历程。
Code
感谢rqy巨佬的代码,让我意识到了Splay可以有多短,以及我之前的Splay有多么的丑...
int fa[N], ch[N][2], rev[N], sz[N], n;
void upd(int x) { sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1; }
void pd(int x) {
if (rev[x]) {
std::swap(ch[x][0], ch[x][1]);
rev[ch[x][0]] ^= 1;
rev[ch[x][1]] ^= 1;
rev[x] = 0;
}
}
int dir(int x) { return x == ch[fa[x]][1]; }
void rot(int x) {
int f = fa[x], d = dir(x);
if (fa[x] = fa[f]) ch[fa[x]][dir(f)] = x;
if (ch[f][d] = ch[x][d^1]) fa[ch[f][d]] = f;
fa[ch[x][d^1] = f] = x;
upd(f); upd(x);
}
int st[N];
void splay(int x, int t = 0) {
int top = 0;
for (int i = x; fa[i]; i = fa[i]) st[top++] = fa[i];
while (top--) pd(st[top]);
pd(x);
for (; fa[x] != t; rot(x)) if (fa[fa[x]] != t) rot(dir(fa[x]) == dir(x) ? fa[x] : x);
}
int kth(int k, int t) {
int o = t;
while (1) {
pd(o);
if (sz[ch[o][0]] == k-1) break;
else if (sz[ch[o][0]] >= k) o = ch[o][0];
else { k -= sz[ch[o][0]]+1; o = ch[o][1]; }
}
splay(o, fa[t]);
return o;
}
void reverse(int l, int r) {
splay(1);
int y = kth(r+1, 1);
int x = ch[y][0];
kth(l-1, x);
rev[ch[ch[y][0]][1]] ^= 1;
}
(以上是维护序列翻转的splay,码量1k多点,唯一的缺陷是压行太严重,不是很好调)
Note
一些小地方
这个Splay的根是fa[i]=0
的点,但是怎么找呢?不用管,直接splay(1)
,这样1
就是根了啊(好暴力啊)。
建树怎么建啊?也不用动脑子啊,直接建成一条链就行了啊,反正之后还要再调整,一开始建的那么完美没有用啊。
关于标记
一般维护序列问题,需要支持几种区间修改操作就打几个标记。
不要忘了在splay
前先pushdown
所有祖先节点。
区间翻转
这个还是比较简单的,直接记录就行。
区间加
这个维护起来也不是很难,就是要注意标记下放的时候对维护的值的处理。
关于应该维护的值
这一部分也是十分的坑啊,只能多做题找感觉了。
区间最值
要注意0号点的值,不要傻乎乎的取最值的时候让0号点影响到答案。
要分开维护每个节点的值和其子树的最值,不要偷懒只存一个啊(我有一个下午都浪费在了这上面...)。