欢迎使用CSDN-markdown编辑器213

前言

  • 妈的我拿着五篇博客对照着看才看懂。
  • 什么是 L i n k C u t T r e e
  • 上至 N O I ,都是一种解决动态树的利器!到底有什么用?
  • 若是有这样一个问题

    维护一棵树,支持下列操作:

    1. 链上求和
    2. 链上求最值
    3. 链上修改
    4. 断开树上的一条边
    5. 连接两个点,保证连接后仍然是一棵树
  • 咋一看,毫无头绪???但是 T a r j a n 大爷早已搞出 L C T (Orz)

  • 动态树因为会动所以很讨厌,不能使用静态的线段树来维护。于是,把树链剖分的线段树换成 S p l a y 试试看?
  • 于是一种算法就出来。 L i n k C u t T r e e = 树链剖分 + S p l a y

原理

  • 树链剖分是如何运作的?
  • 将树划分成重链和轻链,然后每条链用线段树维护。
  • 同样, L C T 也选择重儿子然后连接重链,然而, L C T 的重链更偏向于随缘
  • 我们不必理会重链如何产生,因为树的动态的,所以重链必须不断变换。
  • 对于每条重链,我们用 S p l a y 来维护答案,根据所有重链的 S p l a y ,得出相应答案。

概念

  • 引进如下定义:
    1. P r e f e r r e d     C h i l d 偏爱儿子(重儿子):一个节点最多只有一个偏爱儿子,且偏爱儿子与其父亲同位于一棵 S p l a y 上。与链剖大相近庭的是,这里的偏爱儿子是任意选择的。
    2. P r e f e r r e d     E d g e 偏爱边(重边):顾名思义,就是偏爱儿子与其父亲相连的边。
    3. P r e f e r r e d     P a t h 偏爱路径:由偏爱边连接而成的链。
    4. A u x i l i a r y     T r e e 辅助树:由一条偏爱路径上的所有节点所构成的 S p l a y 称作这条链的辅助树。
  • 可以得出,偏爱路径没有公共点。
  • S p l a y 排序的关键字是节点的深度。因为每个点只能发散出一条偏爱边,故一个深度在 S p l a y 上对应一个节点!!!
  • 每棵 S p l a y 的根节点指向它的父亲(灰色边)。但它的父亲并不指向它。
  • 即爹不认儿子
  • 如下图,橙色边表示 S p l a y ,灰色边表示连接 S p l a y 之间的边。

经过处理偏爱路径的树
任意一条链的辅助树

操作

  • 这里的每个操作都十分重要!!

基本函数

  • 为了方便写程序,定义如下函数

I s R o o t

  • 判断一个点是否为当前所属 S p l a y 的根节点。
  • 可以考虑判断它是否为它父亲的儿子之一。
  • 首先 S p l a y 根节点与它的父亲是虚边。其次,因为“爹不认儿子”,所以它的父亲的儿子中必定没有它。
bool isRoot(int u){
    return son[fa[u]][0]!=u&&son[fa[u]][1]!=u;
}

C h e c k

  • 判断一个点是其父亲的左儿子还是右儿子。
int Check(int u){
    return son[fa[u]][1]==u;
} 

A c c e s s

  • A c c e s s 操作是动态树问题的最重要操作,它的目的是为了能够在一棵 S p l a y 上进行查询,修改,删除,且保证这棵树上只有需要查询的一条链上的点。
  • A c c e s s ( x ) 表示将根节点到 x 这个点的路径变为偏爱链,将 x 与其儿子的所有边都变为非偏爱边
  • 具体如何操作?
    这里写图片描述

  • 我们首先需要把节点 N 往下连出的边变为非偏爱边,即删除 N O 一边。

  • 鉴于 S p l a y 中深度是唯一的,所以我们在 N 所在的 S p l a y 中,把 N 旋转为根,其右儿子必定为点 O ,删除即可(将右儿子标记为 0 )。
  • 往上走,我们需要把 I K 这条边变为轻边,把 I L 变成重边。
  • 前者与上面的思路一样,后者直接将 I 的右儿子置为 L 即可。
  • 以此类推,可以完美解决这个操作。
  • 就不难得到代码。别人的代码打得很巧妙我就Copy了
void access(int u){
    for(int v=0;u;v=u,u=fa[u])
        splay(u),ch[u][1]=v,update(u);//旋转为根,删除/连接右节点,更新当前点
}

M a k e r o o t

  • M a k e r o o t 也是动态树的最重要的操作。
  • M a k e r o o t ( x ) 表示将 x 变为 L i n k C u t T r e e 的根。
  • 考虑这个操作的意义。对于许多操作来说,都是询问一条经过根节点的路径,然而因为 L i n k C u t T r e e 的构造使得这样的问题做起来十分棘手。但是对于只查询根节点一侧的边时,这样的问题便变得十分简单。于是考虑换根。
  • 这里有一个很巧妙的思路。
  • 首先 A c c e s s ( x ) ,使得根节点到 x 的路径在同一棵 S p l a y 上。
  • 这时候的 x 是没有右儿子的。因为没有比它更深的点。
  • 此时我们 S p l a y ( x ) ,将 x 变为根。我们希望 x 同样不含右儿子。
  • 但是实际上, x 将会变得没有左儿子
  • 怎么办?
  • 于是有一个骚操作:直接将 x 的左右子树翻转
  • 这样以后, x 没有右儿子了。
  • 虽然很奇怪,但是确是对的。
void makeroot(int x) {//把x改为原树的根节点 
    access(x); splay(x);
    lazy[x]^= 1;//更新翻转标记
}

猜你喜欢

转载自blog.csdn.net/fengyingjie2/article/details/80292314