动态树问题的解法基于树链的维护。维护需要用可合并的平衡树,因此首选 splay 。
我们将树链上的边称为 preferred edge,将非树链上的边的父亲称为 path parent 。我们将一条相连的 preferred edge 用 splay 以深度为关键字维护,然后 splay(树链) 和 splay 之间连接的边即为 path parent 边。显然,对于一个 splay,可以认为其中所有的节点共有一个 path parent(该树链的上方一个节点)。
需要实现的功能为 cut 分离树, link 连接树,和 维护树上的信息。
首先有 link-cut-tree 的基本操作:access(node * p) 。这个操作的功能是合并出从 p 到其所在树的根的 一条树链。我们每次,只需要将 p 旋到其所在的树链(是一颗 splay 的形态),此时,p 的左子树表示 p 所在树链中 p 的上方的所有节点,p 的右子树表示 p 所在树链中 p 的下方的所有节点。我们可以先将 p 的 path parent 旋到其所在 splay 的根,然后摘除其右子树,再链上 p 及其所在 splay(树链),再将其原右子树的 path parent 设为其本身。此时,我们合并了 p 所在树链 和 p 的 path parent 所在树链,而 p 的 path parent 的原右子树(原下方树链)则被分离出去。每次都这样做,直到达到树的根。
对于 cut(node * p),其功能是将 p 所在子树与 p 的父亲分离,即摘除 p 的所在子树。我们只需要将 p 旋到其所在 splay 的根节点,同上,此时 p 的左子树表示 p 所在树链中 p 的上方的所有节点,p 的右子树表示 p 所在树链中 p 的下方的所有节点。我们将其左子树与之断开,此时勿忘将其左子树(上方树链)的 path parent 设为其path parent,在将其根及 path parent 设为 null,便完成了。
对于 link(node * p, node * q),其功能是将 p 所在子树链至 q 下。很简单,我们先将 p 旋到跟,再修改 p 所在子树的 path parent 为 q,即可。
接下来讲一下实现。
对于 splay 中的 parent 和 path parent 的区分,我们不仅要写两个指针分别表示之,而且特判将变得麻烦。我们可以这样做:将节点与其 path parent 之间的连边实现为:只有父亲指向 path parent,而 path parent 的 儿子 并不指向之。显然让 splay 的根节点的根指向之最合适。这就是上文中,每次使用 path parent 之前,我总会将节点旋到跟的原因。
而对于 preferred edge,这里仅仅只有一个概念而已,其表现方式便是以深度为关键字的 splay ,而并没有实体。也就是说,splay 中的每一条边仅仅是表示一个深度的关系,与树中的边根本不是一回事。
这样,我们只需要稍稍修改 splay 的实现就可以完成了:在旋转中,如果父亲的父亲并没有儿子指向其父亲,那么便不把父亲的父亲的儿子指向之;splay 中判断根的语句更是需要加上当其父亲的儿子不为其本身。
下面上 HNOI 2010 弹飞绵羊 Code:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <climits>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 200005
int n, m, ii, j, k;
struct node {node * pt, * ch[2]; int size;};
node * null = new node(), * a[maxn];
node * newnode()
{
node * p = new node();
p->pt = p->ch[0] = p->ch[1] = null, p->size = 1;
return p;
}
node * update(node * p)
{
if (p == null) return null;
p->size = p->ch[0]->size + p->ch[1]->size + 1;
return p;
}
void rotate(node * p)
{
node * q = p->pt;
bool flag = p == q->ch[0];
p->ch[flag]->pt = q, q->ch[! flag] = p->ch[flag], p->ch[flag] = q;
if (q == q->pt->ch[0]) q->pt->ch[0] = p;
if (q == q->pt->ch[1]) q->pt->ch[1] = p; //Special attention paid
p->pt = q->pt, update(q)->pt = p;
}
bool root(node * p) {return p->pt == null or p != p->pt->ch[0] and p != p->pt->ch[1];}
node * splay(node * p)
{
while (not root(p))
if (root(p->pt)) rotate(p);
else if ((p == p->pt->ch[0]) xor (p->pt == p->pt->pt->ch[0])) rotate(p), rotate(p);
else rotate(p->pt), rotate(p);
return update(p);
}
node * access(node * p)
{
node * q = null;
while (p != null) splay(p)->ch[1] = q, q = update(p), p = p->pt;
return q;
}
int main()
{
freopen("bounce.in", "r", stdin);
freopen("bounce.out", "w", stdout);
scanf("%d", & n);
for (int i = 0; i < n; ++ i)
{
scanf("%d", & k), k = min(i + k, n);
if (! a[i]) a[i] = newnode();
if (! a[k]) a[k] = newnode();
a[i]->pt = a[k];
}
for (scanf("%d", & m); m; -- m)
{
scanf("%d%d", & ii, & j);
if (ii == 1)
printf("%d\n", access(a[j])->size - 1);
else
{
scanf("%d", & k), k = min(j + k, n);
splay(a[j])->ch[0]->pt = a[j]->pt, a[j]->ch[0] = null;
update(a[j])->pt = a[k];
}
}
return 0;
}