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. 微信小程序_(组件)可拖动movable-view

    微信小程序movable-view组件官方文档 传送门 Learn 一.moveable-view组件 一.movable-view组件 direction:movable-view的移动方向,属性值 ...

  2. 线程系列3--Java线程同步通信技术

    上一篇文章我们讲解了线程间的互斥技术,使用关键字synchronize来实现线程间的互斥技术.根据不同的业务情况,我们可以选择某一种互斥的方法来实现线程间的互斥调用.例如:自定义对象实现互斥(sync ...

  3. springboot加载bean过程探索

    springboot一般通过以下main方法来启动项目 @SpringBootApplication public class DemoApplication { public static void ...

  4. 实验三《敏捷开发与XP实践》_实验报告

    实验三<敏捷开发与XP实践>_实验报告 一.实验内容和步骤 提交点1: 任务要求: 实验三 敏捷开发与XP实践 http://www.cnblogs.com/rocedu/p/479577 ...

  5. LC 961. N-Repeated Element in Size 2N Array【签到题】

    In a array A of size 2N, there are N+1 unique elements, and exactly one of these elements is repeate ...

  6. tableau extension 调研

    概述 最近调研了一下 tableau extensions 的实现,有一些心得,记录下来,供以后开发时参考,相信对其他人也有用. 总的来说,写 tableau extensions 还是挺简单的,只是 ...

  7. python的XML解析

    http://www.jb51.net/article/63780.htm http://www.runoob.com/python/python-xml.html http://kb.cnblogs ...

  8. html 网页源码解析:bs4中BeautifulSoup

    from bs4 import BeautifulSoup result=requests.request("get","http://www.baidu.com&quo ...

  9. FTP文件上传和下载(JAVA)

    前文 1.使用FTP的方式进行文件的上传和下载(非SFTP) 2.本人手打,亲测,代码是最简单的,清晰易懂,需要的同学请结合自己的实际添加业务逻辑 2.第三方的jar包:import org.apac ...

  10. SQLSERVER大批量数据快速导入Redis

    目的 把单表近5千万的某单个字段导入到Redis,作为一个list存储. 方案一: 使用sqlcmd工具(sqlserver自带),直接生成命令在Redis-cli中执行. 方案一. 使用sqlcmd ...