动态树(LCT)初探

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/C20190102/article/details/102505969

前置技能

  • Splay基本操作不是前置技能,你只需要知道Splay是一个排序二叉树;(由于某些原因,Splay的文章可能会永远咕掉,也可能明天就出现 逃 ─=≡Σ(((つ•̀ω•́)つ)
  • 指针是前置技能,这时候又要祭出这篇文章了哈哈哈哈哈哈。

那么直接开始吧

原树与辅助树

动态树(Link Cut Tree,LCT),中文和英文对不上,但是却反映了这玩意的两个特征:

  • 支持动态操作,如不断地加边,删边;
  • 把树砍成一条一条的。

这是今天被解剖的树:
一棵很重要的树-1
上面这个被称为原树(represented tree)。

每个结点有一个优先儿子(preferred son),这个优先儿子是谁无所谓。每个结点和它的优先结点之间的边叫优先边(preferred edge),现在我随便选优先儿子,然后把所有优先边加粗:
一棵很重要的树-2

把每个由粗边相连的连通块(包括单点)做成Splay树,众所周知,Splay是排序二叉树,我们需要有个关键字,每个结点的关键字是它的深度,于是它变成了这样(当然,每个Splay的结构也可能有变化,这里给出其中一种):
重要-3
上面这个被称为辅助树(auxiliary tree),也称为路径树(path tree),我直接叫Splay树。

然后,将原树上的细边(非优先边)以虚边的形式接在Splay上(虚边是仅由儿子向父亲连的边):
这张图我画了一年
注意粗边是有左右儿子之分的,因为是Splay,虚边没有。

来张合照,并确保你看懂了这个合照:
合照

Access操作

含义

意思很简单,Access(u)表示,把原树上u到根的所有边设置成优先边。与这条路径冲突的优先边全部变成非优先边。

上图,这是原树上Access(13)的过程:
Access(13)
合照:
合照
当然,上面这个跟实际的代码实现无关,我们全部操作都要在辅助树上完成。

我们要补充Splay树的Splay操作,如果你已了解,请忽略。

扫描二维码关注公众号,回复: 7571403 查看本文章

Addition: Splay

用Splay的根本目的不是为了维护序列,而是用Splay操作维护一棵树的形状。

旋转操作

又是这喜闻乐见的图:
平衡树的旋转
也就是说对于Rotate(x)函数,当左儿子时,必须右旋 x x ;反之,必须左旋 x x
你发现这样一次操作过后,二叉查找树的性质没有变,还将 x x 上提了一层。

研究一下这个操作,只涉及三个点: x x y y b b b b 是子树 B B 的根),说白了就是这两件事:

  • 互换 x x y y 的父子关系;
  • b b 送给 y y (因为互换父子关系过后, y y 必定会空出来一个位置给 b b )。

好了旋转讲完了。

Splay操作

Splay(x)表示把x转到根,一直调用Rotate(x)就行了。

注意可以为了保持树的结构好看,有双旋操作:
这种情况先转 y y 再转 x x 会让树更好看,自己画画图就知道了:
双旋-1
这种情况则连续转两次 x x 更好看:
双旋-2
好了Splay讲完了。

辅助树上的Access

给你看一下辅助树上Access(13)的过程:

Node* Access(Node *u){
    Node *r=NIL;
    while(u!=NIL){
        Splay(u);
        SetChild(u,r,1);
        r=u,u=u->fa;
    }
    return r;
}

(本来代码的位置都是动图的)
不想再做动图了,真的累,,,自己画画想想就明白这个过程了(我其实很讨厌这句话),,,有人看我再做吧,,,好博客没人看很烦呐(打广告于无形之中)。

MakeRoot操作

MakeRoot(u)表示把 u u 变成原树的根。
图就不做了,预计这博客是不可能火的。

void MakeRoot(Node *u){
    Access(u);
    Splay(u);
    u->rev^=1;//Splay的关键字是深度,所以MakeRoot(u)过后要翻转左右子树,想想就明白了,,,,
}

Link操作

表示在原树中把 u u v v 连起来(保证之前 u u v v 不连通)。

void Link(Node *u,Node *v){
    MakeRoot(u);
    u->fa=v;
}

Cut操作

表示断开 u u v v 之间的边(保证有边)。

void Cut(Node *u,Node *v){
    MakeRoot(u);
    Access(v);
    Splay(v);
    u->fa=NIL;
    v->ch[0]=NIL;
}

GetRoot操作

得到 u u 所在的原树的根(因为原树可能是个森林)。

Node* GetRoot(Node *u){
    Access(u);
    Splay(u);
    Node *r=u;
    while(r->ch[0]!=NIL)
        r=r->ch[0];
    Splay(r);
    return r;
}

两道例题

Query on a tree
Dynamic Tree Connectivity

后记

这篇文章写到后面就不想写下去了,做好一篇博客真的很耗时间,但是我绝对敢说我的每篇博客都是网上你能找到的同类博客中最好的之一,我是一个完美主义者,只是想到以前很多真正优秀的博客不能呈现出来,写博客的动力就没有了,,,希望大家多支持分享一下,也给我一些动力吧。我退役前可能会有一天来把这篇博客完善好的,如果大家支持的话;当然,也可能永远停更,如果我觉得写博客没有任何意义的话(毕竟,我个人以为博客对大家的意义远大于对作者的意义)。感激不尽!

猜你喜欢

转载自blog.csdn.net/C20190102/article/details/102505969
今日推荐