Splay
1 : r o t a t e 1:rotate 1:rotate操作无论是左边旋到右边还是右边旋到左边,节点的相对高度都上升了。
2 : 2: 2:单旋 s p l a y splay splay可能会被卡掉,一条链单旋过后还是一条链。
3 : 3: 3:双旋分一下几种情况: ( x − > t o ) (x->to) (x−>to)
-
t o to to是 x x x的 f a t h e r father father,此时直接把 x x x旋转上去就好。
-
x x x和 f a [ x ] fa[x] fa[x]和 f a [ f a [ x ] ] fa[fa[x]] fa[fa[x]]在同一条线上,此时先上旋 f a [ x ] fa[x] fa[x],再上旋 x x x。
-
x x x和 f a [ x ] fa[x] fa[x]和 f a [ f a [ x ] ] fa[fa[x]] fa[fa[x]]不在同一条线上,此时把 x x x上旋两次就好了。
支持如下操作:
- 加入权值为 v v v的点
- 删除权值为 v v v的点
- 查询排名为 x x x的数
- 查询 x x x的排名
- 查询 x x x的前驱
- 查询 x x x的后继
比 f h q − t r e a p fhq-treap fhq−treap难写很多。。
一些注意的地方:
-
因为我们双旋要涉及到爷节点,所以根应该还要有一个父亲节点 0 0 0。根的 f a t h e r father father是这个 0 0 0。
-
节点的编号是永不改变的,改变的只是节点在树中的相对位置。如果把 0 0 0节点视作超级根的话,根是可以改变的。
-
注意我们 s p l a y splay splay的时候要把 t o to to设置为 f a t h e r [ t o ] father[to] father[to]。其原因是如果我们单纯判断 x x x != t o to to 的话是错误的。画个图可以知道,当 x x x被转到根的时候,原来的 t o to to早都不知道转到哪里去的。而 f a t h e r [ t o ] father[to] father[to]以上的结构是绝对不会变化的。换句话说, t o to to是可以在这次 s p l a y splay splay里变得,所以不能用 t o to to的任何信息来判断是否到头。要么事先存下来终点。
-
每一次 r o t a t e rotate rotate之后,树的形态就改变了,所以要 p u s h u p ( k ) pushup(k) pushup(k)的节点信息。
-
这题排名的意思应该是,如果没有重复,排名就是小于它的数的个数再减1,如果有重复。这个数会占用一段排名,但是认为最小的排名才代表这个数的排名。
#include <cstdio>
#include <iostream>
#define ls(x) (tr[x].ch[0])
#define rs(x) (tr[x].ch[1])
#define rt (tr[0].ch[1])
using namespace std;
const int N = 1e5 + 5, INF = 0x3f3f3f3f;
struct poi {
int ch[2], siz, fa, cnt, v;
};
struct Splay {
poi tr[N];
int tot = 0;
inline int newnode(int v, int fa) {
tr[++tot].v = v;
tr[tot].siz = tr[tot].cnt = 1;
tr[tot].ch[0] = tr[tot].ch[1] = 0;
tr[tot].fa = fa;
return tot;
}
inline void connect(int x, int fa, int how) {
tr[x].fa = fa;
tr[fa].ch[how] = x;
}
inline int witch(int x) {
return (tr[tr[x].fa].ch[1] == x);
}
inline void pushup(int k) {
tr[k].siz = tr[ls(k)].siz + tr[rs(k)].siz + tr[k].cnt;
}
inline void rotate(int x) {
int y = tr[x].fa, z = tr[y].fa;
int wic = witch(x), wic2 = witch(y);
connect(tr[x].ch[wic ^ 1], y, wic);
connect(y, x, wic ^ 1);
connect(x, z, wic2);
pushup(y); pushup(x);
}
inline void splay(int x, int to) {// 为什么有人令to为to的父亲?
to = tr[to].fa;// 为什么要是father呢?
while (tr[x].fa != to) {
//printf("%d %d\n", x, to);
int y = tr[x].fa, z = tr[y].fa;
int wic = witch(x), wic2 = witch(y);
if (z == to) {rotate(x); return ;}
else if (wic == wic2) {rotate(y); rotate(x);}
else rotate(x), rotate(x);
}
}
inline void ins(int v) {
int cur = rt;
if (!cur) { rt = newnode(v, 0); return ;} // a
while (1) {
tr[cur].siz++; //printf("cur = %d\n", cur);
if (tr[cur].v == v) {tr[cur].cnt++; splay(cur, rt); return ;}// a
int nxt = (v < tr[cur].v) ? 0 : 1;
if (!tr[cur].ch[nxt]) {
tr[cur].ch[nxt] = newnode(v, cur);
splay(tr[cur].ch[nxt], rt);
return ;
}
cur = tr[cur].ch[nxt];
}
}
inline int find(int v) {
int cur = rt;
while (1) {
if (!cur) return -1;
if (tr[cur].v == v) {splay(cur, rt); return cur;}
int nxt = v < tr[cur].v ? 0 : 1;
cur = tr[cur].ch[nxt];
}
}
inline void del(int v) {
int cur = find(v);
if (cur == -1) return ;
if (tr[cur].cnt > 1){ tr[cur].siz--; tr[cur].cnt--; return ;}
if (!tr[cur].ch[0] && !tr[cur].ch[1]) {
rt = tot = 0;// ???
return ;
}
else if (!tr[cur].ch[0]) {
rt = tr[cur].ch[1];
tr[rt].fa = 0;
return ;
}
else {
int nxt = tr[cur].ch[0];
while (tr[nxt].ch[1]) nxt = tr[nxt].ch[1];
splay(nxt, tr[cur].ch[0]);
connect(tr[cur].ch[1], nxt, 1);
connect(nxt, 0, 1);
pushup(nxt);
}
}
inline int rnk(int x) {
int cur = rt, res = 0;
while (1) {
//printf("cur = %d curv = %d ls = %d rs = %d lsv = %d rsv = %d cnt = %d res = %d\n", cur, tr[cur].v, ls(cur), rs(cur), tr[ls(cur)].siz, tr[rs(cur)].siz, tr[cur].cnt, res);
if (tr[cur].v == x) {res += tr[ls(cur)].siz + 1;splay(cur, rt); break;} //这里要先加再旋
else if (tr[cur].v < x) {
res += tr[ls(cur)].siz + tr[cur].cnt;
cur = rs(cur);
}
else cur = ls(cur);
}
return res;
}
/*inline int atrnk(int c) {
int cur = rt;
while (1) {
if (tr[ls(cur)].siz + 1 <= c && c <= tr[ls(cur)].siz + tr[cur].cnt) {splay(cur, rt); return tr[cur].v;}
else if (tr[ls(cur)].siz + tr[cur].cnt < c) {
c -= tr[ls(cur)].siz + tr[cur].cnt;
cur = rs(cur);
}
else c = ls(cur);
}
}*/
inline int atrnk(int c) {
int cur = rt;
while (1) {
int used = tr[cur].siz - tr[ tr[cur].ch[1] ].siz;
if (c > tr[ tr[cur].ch[0]].siz && c <= used) break;
if (c < used) cur = tr[cur].ch[0];
else {
c -= used;
cur = tr[cur].ch[1];
}
}
splay(cur, rt);
return tr[cur].v;
}
inline int pre(int x) {
int cur = rt, ans = -INF;
while (1) {
if (!cur) return ans;
if (tr[cur].v < x) {
ans = max(ans, tr[cur].v);
cur = rs(cur);
}
else cur = ls(cur);
}
}
inline int suf(int x) {
int cur = rt, ans = INF;
while (1) {
if (!cur) return ans;
if (tr[cur].v > x) {
ans = min(ans, tr[cur].v);
cur = ls(cur);
}
else cur = rs(cur);
}
}
}T1;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
int main() {
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
n = read();
while (n--) {
int opt = read(), x = read();
switch (opt) {
case 1: {T1.ins(x); break;}
case 2: {T1.del(x); break;}
case 3: {printf("%d\n", T1.rnk(x)); break;}
case 4: {printf("%d\n", T1.atrnk(x)); break;}
case 5: {printf("%d\n", T1.pre(x)); break;}
case 6: {printf("%d\n", T1.suf(x)); break;}
}
//puts("kk");
}
return 0;
}
s p l a y splay splay这玩意可能除了写写 L C T LCT LCT之外我不打算用它做任何的事情,难写且易错。
不如 f h q − t r e a p fhq-treap fhq−treap更加实用于考场!!!
LCT
LCT只需要用到 s p l a y splay splay的两个核心操作。 r o t a t e rotate rotate和 s p l a y splay splay,且这里的 s p l a y splay splay还是旋转到这个树的根的。
LCT函数解析:
pushup(k)
这是为了统计 s p l a y splay splay子树内的信息,说是子树。不过你后面 s p l i t split split过后,只有一条链会在这个子树里面了。所以实际上处理的就是链的信息。
inline void pushup(int k) {
tr[k].v = tr[tr[k].ch[0]].v ^ tr[tr[k].ch[1]].v ^ val[k];
}
pushdown(k)
下传标记。我的写法是一个点如果有 t a g tag tag,说明它还没有被翻转。(和线段树写法稍微有些不同)
inline void pushdown(int x) { // 自己还未转过
if (tr[x].rev) {
tr[x].rev ^= 1;
swap(tr[x].ch[0], tr[x].ch[1]);
tr[tr[x].ch[0]].rev ^= 1;
tr[tr[x].ch[1]].rev ^= 1;
}
}
rotate
inline void rotate(int x) {
int y = tr[x].fa, z = tr[y].fa, wic1 = witch(x), wic2 = witch(y), w = tr[x].ch[wic1 ^ 1];
if (notrt(y)) tr[z].ch[wic2] = x; tr[y].ch[wic1] = w; tr[x].ch[wic1 ^ 1] = y;
if (w) tr[w].fa = y; tr[x].fa = z; tr[y].fa = x;
pushup(y);
}
这里就是仿照 s p l a y splay splay的 r o t a t e rotate rotate,不过千万要判断 y y y是不是这个 s p l a y splay splay的根。否则就有可能沟通了若干个 s p l a y splay splay导致错误。
splay
inline void splay(int x) {
int y = x; top = 0;
a[++top] = y;
while (notrt(y)) a[++top] = y = tr[y].fa;
while (top) pushdown(a[top--]);
while (notrt(x)) {
//printf("splay : x = %d\n", x);
int y = tr[x].fa, z = tr[y].fa;
int wic1 = witch(x), wic2 = witch(y);
if (notrt(y)) rotate(wic1 == wic2 ? y : x);
rotate(x);
}
pushup(x);
}
这里要注意标记的处理。下传标记一定要是自上而下传的,所以要用个栈来存下。
access
沟通 x x x和当前的根。使得这条根上面全是重路径。由于LCT认父不认子的性质,一个点 x x x的 f a fa fa是可以和它不在一个 s p l a y splay splay中的。
inline void access(int x) {
int y = 0;
do {
//printf("x = %d fa = %d\n", x, tr[x].fa);
splay(x); tr[x].ch[1] = y; pushup(x); y = x; x = tr[x].fa;
} while(x);
}
remov
强制转换 s p l a y splay splay里这个点的左右孩子关系
inline void remov(int x) {
swap(tr[x].ch[0], tr[x].ch[1]);
tr[tr[x].ch[0]].rev ^= 1;
tr[tr[x].ch[1]].rev ^= 1;
}
makert
把 x x x变为这个树的根。需要先打通路径,再转到 s p l a y splay splay的根。然后再往下翻转。这里注意标记的处理。因为原来树的根的 d e p dep dep是最小的,所以我们期望的 s p l a y splay splay是点 x x x只有右子树的一条链。这里左右孩子交换就把左子树交换到右子树去了,就可以让 x x x深度最小。实现很简单。
inline void makert(int x) {
access(x); splay(x); remov(x);
}
split(x,y)
把 x − y x-y x−y这条路径上提出来一个 s p l a y splay splay,并使得 y y y是根。
inline void split(int x, int y) {
makert(x); access(y); splay(y);
}
findrt(x)
找到 x x x所在 s p l a y splay splay的根节点。 x x x转到 s p l a y splay splay的根,然后一直往左子树走就好。注意这里操作完成之后,原来的 x x x就是 s p l a y splay splay的新根了。
inline int findrt(int x) {
access(x); splay(x);
while (tr[x].ch[0]) pushdown(x), x = tr[x].ch[0];
return x;
}
link(x,y)
连接 x , y x,y x,y这条边,在 s p l a y splay splay上保证 y y y是根节点。(我们 f i n d r t findrt findrt之后, s p l a y splay splay的根节点就是 y y y了,所以 x x x得父亲要是 y y y)
inline void link(int x, int y) {
makert(x);
if (findrt(y) != x) tr[x].fa = y;
}
cut(x,y)
删除 x , y x,y x,y这条边。因为 x , y x,y x,y的距离为1,所以在 s p l a y splay splay上它们是相邻的两个点,需要判断不合法的情况。
inline void cut(int x, int y) {
makert(x);
if (findrt(y) != x || tr[x].fa != y || tr[x].ch[1]) return ;
tr[x].fa = tr[y].ch[0] = 0;
pushup(y);
}
f
i
n
d
r
t
(
y
)
!
=
x
findrt(y)!=x
findrt(y)!=x是用来判断在不在一个
s
p
l
a
y
splay
splay里面。
t
r
[
x
]
.
f
a
!
=
y
和
t
r
[
x
]
.
c
h
[
1
]
tr[x].fa!=y和tr[x].ch[1]
tr[x].fa!=y和tr[x].ch[1]是用来判断
x
x
x和
y
y
y的两个点距离是不是1。
考虑如果不是1的话,至少有一个条件会被满足。
注意修改 x x x的时候,要先把 x x x旋转到根再修改。是为了改变它的孩子的信息。从根往下下传标记。而且这样改不需要修改 x x x所在 s p l a y splay splay的其他的根。
完整代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n, m, val[N], top, a[N];
struct LCT {
struct poi {
int ch[2], fa, v, rev;
}tr[N];
inline void pushup(int k) {tr[k].v = tr[tr[k].ch[0]].v ^ tr[tr[k].ch[1]].v ^ val[k];}
inline void pushdown(int k) {
if (tr[k].rev) {
tr[k].rev = 0;
swap(tr[k].ch[0], tr[k].ch[1]);
tr[tr[k].ch[0]].rev ^= 1;
tr[tr[k].ch[1]].rev ^= 1;
}
}
inline int witch(int x) {return (tr[tr[x].fa].ch[1] == x);}
inline int notrt(int x) {return (tr[tr[x].fa].ch[0] == x || tr[tr[x].fa].ch[1] == x);}
inline void rotate(int x) {
int y = tr[x].fa, z = tr[y].fa, wic1 = witch(x), wic2 = witch(y), w = tr[x].ch[wic1 ^ 1];
if (notrt(y)) tr[z].ch[wic2] = x; tr[x].ch[wic1 ^ 1] = y; tr[y].ch[wic1] = w;
if (w) tr[w].fa = y; tr[x].fa = z; tr[y].fa = x;
pushup(y);
}
inline void splay(int x) {
int y = x; top = 0;
a[++top] = y;
while (notrt(y)) a[++top] = y = tr[y].fa;
while (top) pushdown(a[top--]);
while (notrt(x)) {
int y = tr[x].fa, z = tr[y].fa;
int wic1 = witch(x), wic2 = witch(y);
if (notrt(y)) rotate(wic1 == wic2 ? y : x);
rotate(x);
}
pushup(x);
}
inline void access(int x) {
int y = 0;
do {
splay(x); tr[x].ch[1] = y; pushup(x); tr[y].fa = x; y = x; x = tr[x].fa; // ???
} while (x);
}
inline void remov(int x) {
swap(tr[x].ch[0], tr[x].ch[1]);
tr[tr[x].ch[0]].rev ^= 1;
tr[tr[x].ch[1]].rev ^= 1;
}
inline void makert(int x) {
access(x); splay(x); remov(x);
}
inline void split(int x, int y) {
makert(x); access(y); splay(y);
}
inline int getrt(int x) {
access(x); splay(x); // access
while (tr[x].ch[0]) pushdown(x), x = tr[x].ch[0];
return x;
}
inline void link(int x, int y) {
makert(x);
if (getrt(y) != x) tr[x].fa = y;
pushup(y); // ???
}
inline void cut(int x, int y) {
makert(x);
if (getrt(y) != x || tr[x].ch[1] || tr[x].fa != y) return ;
tr[x].fa = tr[y].ch[0] = 0;
pushup(y);
}
}T;
int main() {
n = read(); m = read();
for (int i = 1; i <= n; ++i) val[i] = read();
for (int i = 1; i <= m; ++i) {
int opt = read(), x = read(), y = read();
if (opt == 0) {T.split(x, y); printf("%d\n", T.tr[y].v);}
if (opt == 1) T.link(x, y);
if (opt == 2) T.cut(x, y);
if (opt == 3) {T.splay(x); val[x] = y;}
}
return 0;
}
助记:一旦涉及到孩子变化的,都要pushup根节点。split,link,cut都实现要选定好一个点为根。split因为要把这个路径splay割出来所以要access。getrt(x)之后x这个点就到根了。无他,唯手熟尔。。。
注意上面这玩意维护的是一个森林,它不是一个图。它保证时时刻刻只是树边。它是动态树连通性而不是tm的动态图连通性。在线的动态图连通性比较复杂。。。
bzoj2002
每个点朝他的后继节点连边,跳出去的就建立一个虚拟点都连向他。就要支持加边,删边,维护点到这个虚拟点的距离。考虑每个点最多只有一个后继节点,把它反过来看就是一个严格树的形态。就是LCT了吧。
距离这个东西还是维护一个像splay的子树和的东西解决。考虑最后问的形态一定是一个链。 注意这个dep表示的是一个点到它下边的最深孩子的节点个数,减一就是答案了。
luoguP2147
这题才是LCT维护动态树连通性的裸题啊哈哈哈。直接以一个点为根,getfa一下就好了。