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. 2018-2019-2 20165205 网络对抗技术 Exp7 网络欺诈防范

    2018-2019-2 20165205 网络对抗技术 Exp7 网络欺诈防范 实验内容 本次实践的目标理解常用网络欺诈背后的原理,以提高防范意识,并提出具体防范方法.具体实践有 (1)简单应用SET ...

  2. ftp相关

    已经存在虚拟账户 添加新账户 .在/etc/vsftpd/user.txt里面配置用户名密码 单行是用户名 双行是密码 .导入新用户密码 db_load -T -t hash -f /etc/vsft ...

  3. 编译openwrt时报错build_dir/hostpkg/libubox-2018-07-25-c83a84af/blobmsg_json.c:21:19: fatal error: json.h: No such file or directory

    答: 一. 详细日志: build_dir/hostpkg/libubox-2018-07-25-c83a84af/blobmsg_json.c:21:19: fatal error: json.h: ...

  4. LC 962. Maximum Width Ramp

    Given an array A of integers, a ramp is a tuple (i, j) for which i < j and A[i] <= A[j].  The ...

  5. java web 开发快速宝典 ------电子书

    http://www.educity.cn/jiaocheng/j10259.html 1.2.1  JDk 简介 JDK是Sun公司在1995年推出的一套可以跨操作系统平台编译和运行Java程序的开 ...

  6. 非监督的降维算法--PCA

    PCA是一种非监督学习算法,它能够在保留大多数有用信息的情况下,有效降低数据纬度. 它主要应用在以下三个方面: 1. 提升算法速度 2. 压缩数据,减小内存.硬盘空间的消耗 3. 图示化数据,将高纬数 ...

  7. 美化Eclipse-背景

    为了美化Eclipse,请登录主题网站http://www.eclipsecolorthemes.org/ 下载EPF配置文件(截图如下),并导入eclispe即可. 导入方法: (1)从File菜单 ...

  8. 转: Android 设备的远程调试入门

    从 Windows.Mac 或 Linux 计算机远程调试 Android 设备上的实时内容. 本教程将向您展示如何: 设置您的 Android 设备进行远程调试,并从开发计算机上发现设备. 从您的开 ...

  9. RxJava2实战---第六章 条件操作符和布尔操作符

    RxJava2实战---第六章 条件操作符和布尔操作符 RxJava的条件操作符主要包括以下几个: amb():给定多个Observable,只让第一个发射数据的Obsrvable发射全部数据. de ...

  10. java文件夹上传下载控件分享

    用过浏览器的开发人员都对大文件上传与下载比较困扰,之前遇到了一个需要在JAVA.MyEclipse环境下大文件上传的问题,无奈之下自己开发了一套文件上传控件,在这里分享一下.希望能对你有所帮助. 以下 ...