LCT(Link-Cut-Tree)学习笔记

Link-Cut-Tree

以下内容为博主乱写的,难免出错,请神犇不要深究,有错请指出。

Link-Cut-Tree(LCT)是解决动态树类问题一种数据结构

OI主神Tarjan发明的,%(mei)%(you)%(ta)%(cai)%(shi)%(zui)%(hou)%(de)

首先以下黑体字是普通线段树能解决的问题

维护一个序列,支持下列操作:
区间求和
区间求最值
区间修改
求连续子段和
添加一段区间
删除一段区间
翻转一段区间

但是遗憾的是,它无法解决上面的东西,但是对于LCT来说,这就太简单了

维护一棵树,支持下列操作:
链上求和
链上求最值
链上修改
断开树上的一条边(cut)
连接两个点,保证连接后仍然是一棵树(link)

bzoj3282, 这是一道很裸的题,它要求我们写一种数据结构,满足连接两棵树或者断开一条链及能够在树上进行查询

如果是树链剖分的话,显然是不能做的吧,如果硬上的话,时间堪比大暴力或者根本写不出来,因此我们需要一种这样的解决动态树问题的数据结构

大概知道了LCT是干什么的,现在讲一讲如何实现LCT

LCT的基础是Splay(也是Tarjan发明的东西,在这里就不讲了)

正文

大多数同学在初学LCT的时候,都会分不明白什么是Auxiliary Tree(辅助树)

这是我们做LCT的题时真正要维护的东西,而非原树(不要想着去维护原树)

先看一些概念性的东西(或许你会认为和树链剖分一样的东西,但这是不一样的):

Preferred Child:重儿子,重儿子与父亲节点同在一棵Splay中,一个节点最多只能有一个重儿子
Preferred Edge:重边,连接父亲节点和重儿子的边
Preferred Path:重链,由重边及重边连接的节点构成的链

像这个东西


丑爆了有木有,但是记住,一条重链在一棵splay中,而且可以看出,preferred edge 和 树链剖分中的重边 不是一个东西

树链剖分是把一棵树分成一条条链,而LCT维护的,可以是一个森林,然后把这个森林中每棵树Preferred Pathsplay维护

在讲access操作之前,我们需要先知道,一棵splay中的

splay的根节点(B,C,I,L)的father指向原来节点的father

然而其他节点的father却不一定指向原来的father

就是

辅助树的根节点≠原树的根节点
辅助树中的father≠原树中的father

ACCESS 操作是Link-Cut Trees 的所有操作的基础. 假设调用了过程ACCESS(v), 那么从点v 到根结点的路径就成为一条新的Preferred Path. (即与根节点在一棵splay中,这意味着我们可以用splay把它翻上去了)如果路径上经过的某个结点u 并不是它的父亲parent(u) 的Preferred Child, 那么由于parent(u) 的Preferred Child 会变为u , 原本包含parent(u) 的Preferred Path 将不再包含结点parent(u) 及其之上的部分.(大量摘,说明很重要,请仔细阅读)

下面是对N进行access操作时的情况


(请仔细阅读)

N与根结点就完美的在同一棵splay中了

基本操作

access

void access( int x ){
	for( int t = 0; x; t = x, x = fa[x] ){
		splay(x); c[x][1] = t; update(x);
	}
}

move to root

void move_to_root( int x ){
	access(x); splay(x); rev[x] ^= 1;
}

Find Root
在ACCESS(v) 之后, 根结点一定是v 所属的Auxiliary Tree 的最小结点. 我们先把v 旋转到它所属的Auxiliary Tree 的根. 再从v 开始, 沿着Auxiliary Tree 向左走, 直到不能再向左, 这个点就是我们要找的根结点. 由于使用的是Splay Tree 数据结构保存Auxiliary Tree, 我们还需要对根结点进行Splay 操作.

int find_root( int x ){
	access(x); splay(x);
	while( c[x][0] ) x = c[x][0];
	return x;
}
Cut
先访问v, 然后把v 旋转到它所属的Auxiliary Tree 的根, 然后再断开v 在它的所属Auxiliary Tree 中
与它的左子树的连接, 并设置.

void cut( int x, int y ){
	move_to_root(x); access(y); splay(y); fa[x] = c[y][0] = 0;
}
link
先访问v , 然后修改v 所属的Auxiliary Tree 的Path Parent 为w, 然后再次访问v .

void link( int x, int y ){
	move_to_root(x); fa[x] = y;
}

splay的操作

void rotate( int x ){
	int y = fa[x], z = fa[y], l, r;
	l = (c[y][1]==x); r = l^1;
	if( !isroot(y) ) c[z][y==c[z][1]] = x;
	fa[x] = z; fa[y] = x; fa[c[x][r]] = y;
	c[y][l] = c[x][r]; c[x][r] = y;
	update(y); update(x);
}
void splay( int x ){
	sta[++top] = x;
	for( int i = x; !isroot(i); i = fa[i] ) sta[++top] = fa[i];
	while( top ) pushdown(sta[top--]);
	while( !isroot(x) ){
		int y = fa[x], z = fa[y];
		if( !isroot(y) ){
			if( (c[y][0]==x)^(c[z][0]==y) ) rotate(x);
			else rotate(y);
		}
		rotate(x);
	}
}

就说到这里,感觉有些烂尾

另:在能用多种方法做的一道题的所有方法中,LCT基本上是最慢

虽然是O((n + q) log n)但splay自带巨大常数

引用一下论文中的运行时间
解法编号解法概述时间复杂度
解法一 动态树, 使用Link-Cut Trees 数据结构O((n + q) log n)
解法二 轻重边路径剖分, 用线段树或虚二叉树维护每条路径O(n + q log2 n)
解法三 轻重边路径剖分, 用Splay Tree 维护每条路径O((n + q) log n)
解法四 轻重边路径剖分, 用一棵“全局平衡二叉树”维护所有路径O((n + q) log n)
解法编号相应参考程序运行时间
解法一 QTREE dynamic-tree link-cut-trees.pas 4.58 秒
解法二 (线段树) QTREE heavy-light-decomposition segment-tree.pas2.65 秒
解法二 (虚二叉树) QTREE heavy-light-decomposition imaginary-bst.pas2.37 秒
解法三 QTREE heavy-light-decomposition splay-tree.pas4.28 秒
解法四 QTREE heavy-light-decomposition global-balanced-bst.pas2.06 秒

练习题

splay:bzoj3223,bzoj2500

bzoj2002 支持分离,link,cut,find

bzoj2631 基本操作

bzoj3669

bzoj3282

参考文献

Link-Cut-Tree.ppt  by PoPoQQQ

QTREE解法的一些研究

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值