LCT

Upd:

一个细节:假如我们要修改某个节点的数据,那么要先把它makeroot再修改,改完之后pushup。

LCT是一种维护森林的数据结构,本质是用Splay维护实链剖分。

实链剖分大概是这样的:每个节点往一个儿子连实边,其它的儿子连虚边。

而我们用Splay维护实链剖分后的每一条实链。

因此LCT有一些基本的性质:

\(1.\)每一棵Splay维护树上一条直上直下的实链,且其中序遍历的点的序列的深度递增。

\(2.\)每个节点包含且仅包含于一棵Splay。

\(3.\)实边包含于Splay中,而虚边则连接两棵Splay。对于每个节点而言,它会记录它在Splay中的父亲,而play的根节点的父亲则是原树中这棵Splay代表的链的链顶的父亲。但是每个节点只会记录它在Splay中的左右儿子,并不会记录由虚边连接的儿子。(认父不认子

下面如果没有特殊说明,我们默认我们所说的树为Splay而非原树。

然后我们先来说几个预备的基本操作:

nrooot

nroot实现判断一个节点是否为该节点所在Splay的根节点。

根据认父不认子的特性,我们只需要判断该节点的父亲是否有它这个儿子即可。

int nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}

pushrev

pushrev实现把一个子树翻转。

在后面的操作需要用到。

具体为交换左右儿子,并给左右儿子打上翻转标记。

void pushrev(int x){swap(lc,rc),rev[x]^=1;}

pushdown

pushdown实现下放标记。

在后面的操作中我们有翻转整棵子树的标记。

有的题目可能还会有其它的标记。

void pushdown(int x){if(!rev[x])return ;rev[x]=0,pushrev(lc),pushrev(rc);}

pushall

pushall实现把某个点到该点所在Splay根节点路径上的标记全部下放。

在后面的操作需要用到。

可以用栈实现不过函数堆栈更加方便。

void pushall(int x){if(nroot(x))pushall(fa[x]);pushdown(x);}

然后我们来分析一下Splay的基本操作:

这里我们需要支持的基本上就只有rotate和splay两个操作了。

rotate

和一般的Splay没有什么较大的差别。

判断一下当前节点的父亲是否为当前Splay的根,免得破坏认父不认子的特性。

void rotate(int x)
{
int y=fa[x],z=fa[y],k=ch[y][1]==x;
if(nroot(y)) ch[z][ch[z][1]==y]=x;
fa[x]=z,fa[y]=x,fa[ch[x][!k]]=y,ch[y][k]=ch[x][!k],ch[x][!k]=y,pushup(y);
}

splay

和一般的Splay没有什么较大的差别。不过我们现在只需要旋转到根节点了。

不过在splay之前需要pushall一下,典型的查询前pushdown

void splay(int x)
{
pushall(x);
for(int y;nroot(x);rotate(x)) if(nroot(y=fa[x])) rotate((ch[fa[y]][0]==y)^(ch[y][0]==x)? y:x);
pushup(x);
}

然后就是LCT的基本操作了。

LCT所有的操作都依赖于一个核心操作:access。

access

access实现将原树中某个点到根的路径变为一条实链,单独拿出来做一棵Splay。

假设我们要拉的是\(x\)点。

首先我们把\(x\)Splay一下。

这时\(x\)的左子树中的点都会在这条实链上,而右子树的点都不在。所以我们把\(rc\)置为\(0\)。(注意认父不认子的特性)

然后我们跳到\(y=fa_x\)(根据上面的性质,我们跳到的实际上就是原树中\(x\)当前实链链顶的父亲。),把y\(Splay\)一下。

那么此时\(y\)的左子树中的点还是都在这条实链上,而右子树的点都不在。所以我们把刚才的\(x\)接在\(y\)的右儿子处。

注意因为修改后pushup的原则,我们需要在更新右儿子的时候pushup一下。

这样一直做到原树的根为止即可。

void access(int x){for(int y=0;x;x=fa[y=x])splay(x),rc=y,pushup(x);}

makeroot

相比access,makeroot更进一步地实现了把一个节点\(x\)转成原树中的根节点。

原理还是很简单的。我们先把\(x\)access一下,然后把\(x\)splay到它所在Splay的根节点。

此时因为它是原树中这条链上深度最大的点,所以它没有右儿子。

为了让它变成原树中深度最小的点,我们把它的左右子树交换一下,这样他就没有左儿子了,就变成了原树中深度最小的点。

交换子树可以通过打标记的方法来完成。

void makeroot(int x){access(x),splay(x),pushrev(x);}

findroot

findroot实现找到一个节点\(x\)所在原树中的根节点。

和makeroot类似,我们先access、splay\(x\)。

那么此时\(x\)所在原树中的根就是它左儿子中一直跳左儿子最后到的点。

根据查询前pushdown的原则我们要一边跳左儿子一边pushdown。

Upd:根据xzz的说法这里由于我们pushdown的写法可以不用pushdown。

最后可以顺便把查询到的根节点给splay一下。

int findroot(int x){access(x),splay(x);while(lc)x=lc;return splay(x),x;}

split

split实现把原树中一条链\((x,y)\)单独拿出来做一条实链。

很轻松地,我们先让\(x\)做原树的根,然后拉一条\((x,y)\)的实链出来,再让\(y\)做这条实链的Splay的根。这样我们在过程中通过pushup和pushdown就会让这条实链(这棵Splay)上的信息反映到这棵Splay的根节点\(y\)上。

然后如果我们要查询一条路径的信息,就可以快乐地split然后查\(y\)的信息了。

void split(int x,int y){makeroot(x),access(y),splay(y);}

下面则是一些真正意义上LCT能做而树剖做不了的东西了。

link

link实现在两点\(x,y\)之间新连一条边\((x,y)\)。(如果\(x,y\)连通就不连)

保证\(x,y\)不连通:

直接让\(x\)做\(x\)所在原树的根,然后把\(fa_x\)置为\(y\)即可。

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

不保证\(x,y\)不连通:

还要判一下连通性。即\(x\)成为原树所在的根之后,判断\(y\)所在原树的根是否为\(x\)。

void link(int x,int y){makeroot(x);if(findroot(y)^x)fa[x]=y;}

cut

cut实现断掉边\((x,y)\)。(如果不存在边\(x,y\)就不断)。

保证\((x,y)\)存在:

先把\((x,y)\)给split出来。

然后根据split的写法,此时\(y\)一定是该Splay的根,\(x\)一定是该原树的根,所以\(x\)一定是\(y\)的左儿子,那么我们直接断就完事了。

记得pushup。

void cut(int x,int y){split(x,y),fa[x]=ch[y][0]=0,pushup(y);}

不保证\((x,y)\)存在:

先判断连通性。

由于我们makeroot了,所以此时\(x\)一定为原树的根。

又由于我们findroot了,所以此时\(x\)一定是这条链的Splay的根。

那么如果此时\(y\)是\(x\)的右儿子,\(x\)是\(y\)的父亲,且\(y\)没有左儿子(\(x,y\)之间没有其它深度的点),那么\((x,y)\)就是存在的。

事实上如果满足了\(x\)是\(y\)的父亲,\(y\)没有左儿子,那么\((x,y)\)就是存在的。

因为\(x\)不可能有左儿子。

void cut(int x,int y){makeroot(x);if(findroot(y)==x&&fa[y]==x&&!ch[y][0])fa[y]=rc=0,pushup(x);}

这样我们就成功完成了LCT的基本操作。

然后就是一些技巧性的东西了:

LCT维护e-dcc:

删边似乎不太好做?只考虑加边(虽然树剖也能做)。

假如加入的边的两端不连通就直接加入,否则我们就会形成一个e-dcc,此时缩点即可。

LCT维护v-dcc:

圆方树?溜了溜了。

LCT维护边权:

一般而言我们最基本的思路是把边权放到其深度更大的那一端。

但是由于LCT会改变父子关系所以这东西没办法做了。

因此我么考虑拆点,把一条边拆成一个点,向其两端连边。

然后就可以做了。

LCT维护子树:

这是个大头。

如果会Top Tree的话似乎会轻松很多。

对于某些较容易维护的信息,我们可以考虑开一个新的数组来记录每个点的所有轻儿子所在子树的信息和,那么这个点的子树信息和就是两个重儿子的子树信息和加上所有轻儿子的子树信息和了。

(下面的\(sum\)表示该节点所有轻儿子的子树信息和,\(Sum\)表示该节点子树的信息和)

这样对于pushup操作,我们要多加一点东西。

void pushup(int x){Sum[x]=Sum[lc]+Sum[rc]+sum[x]+1;}

对于改变了轻重关系的操作,我们需要实时维护这个东西。比如access。

void access(int x){for(int y=0;x;x=fa[y=x])splay(x),sum[x]+=Sum[rc],sum[x]-=Sum[rc=y];}

link:

\(y\)多了一个轻儿子,所以要进行更改。

所以\(y\)在Splay中的祖先也要跟着更改,这太麻烦了。

我们把\(y\)旋转到它所在Splay的根,就不用了改\(y\)的祖先了。

保证\(x,y\)不连通:

void link(int x,int y){split(x,y),sum[fa[x]=y]+=Sum[x],pushup(y);}

这里的split是一个偷懒的写法。

不保证\(x,y\)不连通:

void link(int x,int y){makeroot(x);if(findroot(y)^x)sum[fa[x]=y]+=Sum[x],pushup(y);}

cut:

断掉的一定是重边,因为我们pushup了所以并没有特殊的影响。

LCT维护染色连通块:

可以直接开一个LCT,把同色的点连起来,不过很辣鸡。

更加优秀一点的是每个颜色开一个LCT。

还有一些诸如把点的颜色赋给其父边的技巧。

动态树(LCT、Top Tree、ETT)的更多相关文章

  1. hdu 5398 动态树LCT

    GCD Tree Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Su ...

  2. hdu 5002 (动态树lct)

    Tree Time Limit: 16000/8000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submi ...

  3. HDU 4718 The LCIS on the Tree (动态树LCT)

    The LCIS on the Tree Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Oth ...

  4. [模板] 动态树/LCT

    简介 LCT是一种数据结构, 可以维护树的动态加边, 删边, 维护链上信息(满足结合律), 单次操作时间复杂度 \(O(\log n)\).(不会证) 思想类似树链剖分, 因为splay可以换根, 用 ...

  5. 动态树LCT小结

    最开始看动态树不知道找了多少资料,总感觉不能完全理解.但其实理解了就是那么一回事...动态树在某种意思上来说跟树链剖分很相似,都是为了解决序列问题,树链剖分由于树的形态是不变的,所以可以通过预处理节点 ...

  6. 动态树LCT(Link-cut-tree)总结+模板题+各种题目

    一.理解LCT的工作原理 先看一道例题: 让你维护一棵给定的树,需要支持下面两种操作: Change x val:  令x点的点权变为val Query x y:  计算x,y之间的唯一的最短路径的点 ...

  7. bzoj2049-洞穴勘测(动态树lct模板题)

    Description 辉辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好 ...

  8. SPOJ OTOCI 动态树 LCT

    SPOJ OTOCI 裸的动态树问题. 回顾一下我们对树的认识. 最初,它是一个连通的无向的无环的图,然后我们发现由一个根出发进行BFS 会出现层次分明的树状图形. 然后根据树的递归和层次性质,我们得 ...

  9. BZOJ 2002: [Hnoi2010]Bounce 弹飞绵羊 (动态树LCT)

    2002: [Hnoi2010]Bounce 弹飞绵羊 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 2843  Solved: 1519[Submi ...

  10. HDU 5002 Tree(动态树LCT)(2014 ACM/ICPC Asia Regional Anshan Online)

    Problem Description You are given a tree with N nodes which are numbered by integers 1..N. Each node ...

随机推荐

  1. Codeforces 979 D. Kuro and GCD and XOR and SUM(异或和,01字典树)

    Codeforces 979 D. Kuro and GCD and XOR and SUM 题目大意:有两种操作:①给一个数v,加入数组a中②给出三个数x,k,s:从当前数组a中找出一个数u满足 u ...

  2. Struts2中的ModelDriven机制及其运用、refreshModelBeforeResult属性解决的问题

    1.为什么需要ModelDriven? 所谓ModelDriven,意思是直接把实体类当成页面数据的收集对象.比如,有实体类User如下: package cn.com.leadfar.struts2 ...

  3. Leetcode题目33.搜索旋转排序数组(中等)

    题目描述: 假设按照升序排序的数组在预先未知的某个点上进行了旋转. ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] ). 搜索一个给定的目标值,如果数组中存在 ...

  4. java实现一个简单的验证码生成器

    最近看了网上很多大佬们写的验证码生成,寻思着自己也写一个,话不多说,代码如下: import java.awt.BasicStroke; import java.awt.Color; import j ...

  5. springboot非web项目

    使用CommandRunner @SpringBootApplication public class CrmApplication implements ApplicationRunner { @A ...

  6. react判断点击位置是否为组件内,实现点击外部触发组件内事件

    1.导入 import {findDOMNode} from 'react-dom' 2.绑定ref <div ref="refTest" </div> 3.绑定 ...

  7. 学习Oracle数据库入门到精通教程资料合集

    任何大型信息系统,都需要有数据库管理系统作为支撑.其中,Oracle以其卓越的性能获得了广泛的应用.本合集汇总了学习Oracle数据库从入门到精通的30份教程资料. 资料名称 下载地址 超详细Orac ...

  8. Oracle数据库提高sql查询效率总结

    我们要做到不但会写SQL,还要做到写出性能优良的SQL语句. (1)选择最有效率的表名顺序(只在基于规则的优化器中有效): Oracle的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句 ...

  9. linux常用命令(19)find xargs

    在使用 find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行.但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出 ...

  10. delphi 权限控制(delphi TActionList方案)

    在软件开发中,为软件加入权限控制功能,使不同的用户有不同的使用权限,是非常重要的一项功能,由其在开发数据库方面的应用,这项功能更为重要.但是,要为一个应用加入全面的权限控制功能,又怎样实现呢?大家知道 ...