ps:推荐论文《QTREE解法的一些研究》by杨哲。
作用
高效解决动态树问题。
思想
Link-Cut Tree(简称LCT)将树的边分为Prefered Edge和普通边,将点分为Prefered Child和普通点,Prefered Edge和Prefered Child是不断变化的,且刚开始只有普通边和普通点。LCT的核心操作Access(x):访问x。
假设最后访问了lst节点,对于节点x,如果lst在x的儿子son的子树中,则son变成x的Prefered Child(原先的Prefered Child变为普通点),连接x和son的边变成Prefered Edge(原先的Prefered Edge变为普通边,由此可见x至多只有一个Prefered Child)。特殊的是,lst没有Prefered Child。那么显然访问了lst之后,lst到ro(ro表示lst所在树的根)之间的边都变成Prefered Edge了。
对于一条由Prefered Edge连接而成的链,我们用数据结构(通常用Splay,因为Splay可以方便的分裂和合并)来维护,将链自顶向下看做一个序列(dep越小越靠前)用Splay储存,且这条链的Splay的根节点储存了一个Path Father,表示这条链顶端节点的father。要注意,这个关系是单向的,也就是说这条链的Splay的根节点的father是Path Father,但Path Father所在Splay和这条链的Splay是不连通的。
假设我们已经实现了Access,那么如何实现Link(x,y)(把y连接到x上)和Cut(x,y)(把y从x上断开)呢?首先我们需要实现一个make_ro(p),作用是把p变成p所在树(注意,不是p所在Splay!)的根。代码如下:
void make_ro(int p) {Access(p);Splay(p);Add_flip(p);}
非常简短,但是好像很费解,我们来看图:
先访问p,这样p到ro之间的边就都变成Prefered Edge了(也就是说p到ro之间的点都在同一个Splay中了),然后把p伸展到p所在Splay的根。由于p是最深的节点,所以其他点均在p的左边,但是p成为了根节点,这条链就被翻转了,加翻转标记。完成这三个操作之后,p就成为了新根。
有了make_ro操作,Link和Cut就很简单了:
void Link(int x,int y) {Access(y);make_ro(y);father[y]=x;}
先把y变成根,然后把father[y]给x就行了(不要把son[x][0/1]给y,因为这是单向连接)。
void Cut(int x,int y)
{
make_ro(y);Access(x);Splay(x);
father[y]=son[x][0]=0;Pushup(x);
}
先把y变成根,然后访问x,访问之后x所在链中就只有x和y两个节点了,伸展x,则y必定是x的左二子。断开连接,更新x。
然而Access还没讲,其实非常暴力,假设目前处理到p,上次处理了lst。那么先断开p与原先Prefered Child之间的连接,然后连接p和lst,更新p。重复这个过程直到p为空。
void Access(int p)
{
int lst=0;
while (p)
{
Splay(p);son[p][1]=lst;Pushup(p);
//先Splay(p),这样son[p][1]就是原先p的Prefered Child的子树了
//将son[p][1]给成lst,更新p
//不用修正father[son[p][1]],因为p成为了father[son[p][1]]的Path Father
lst=p;p=father[p]; //继续处理
}
}
这样不会超时吗?之后再讲。
除了Link和Cut,我们还可以实现以下功能:
1.取出x->y的路径
make_ro(x),Access(y),此时x->y的路径被存在同一棵Splay中。
2.求x和y的LCA
Access(x),Access(y),Splay(x),则LCA=x的Path Father。
特殊:当x没有Path Father时,LCA=x(因为y跨越了x到ro,所以x没有Path Father)。
效率
Splay的总效率为
O((n+Q)∗log2(n))
,但是Access的总效率呢?
Access的效率取决于Prefered Child改变的次数,而Prefered Child改变的次数取决于Prefered Edge改变的次数。Prefered Edge和普通边不禁让我们想到了树链剖分-轻重链剖分中的重边和轻边。根据轻重链剖分的定义,我们会发现每次Access,至多有
log2(n)
条普通边变成了轻的Prefered Edge,也就是说普通边变成轻的Prefered Edge的总次数为
(n+Q)∗log2(n)
。但是有多少普通边变成了重的Prefered Edge呢?我们转化一下,普通边变成重的Prefered Edge的次数=重的Prefered Edge变成普通边的次数+n(可能最后全是重的Prefered Edge),而重的Prefered Edge变成普通边必定意味着一条普通边变成了轻的Prefered Edge,所以普通边变成重的Prefered Edge的次数=
(n+Q)∗log2(n)+n
。
综上所述Access的总效率为
O((n+Q)∗log2(n))
,也就是说LCT的总效率为
O((n+Q)∗log2(n))
。
ps:然而Splay常数巨大,所以LCT常数巨大……