link-cut-tree

动态树问题的解法基于树链的维护。维护需要用可合并的平衡树,因此首选 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;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值