前三篇好像变成了SPLAY专题...

这一篇正式开始LCT!

其实LCT就是基于SPLAY的伸展操作维护树(森林)连通性的一个数据结构

核心操作有很多,我们以一道题为例:

例:bzoj 2049 洞穴勘测

要求:加边和删边,询问连通性

其实如果没有删边,裸跑并查集似乎就可以搞定

但由于存在删边,并查集思想受阻,我们要考虑更高级的数据结构

于是LCT横空出世!

LCT的核心思想:多棵SPLAY维护虚实树链

首先介绍一下树链剖分问题:

树链剖分问题是将一棵树划分成多条树链的思想,有很多种剖分方法,比如轻重树链剖分(最常见,最常用),长短树链剖分(在部分题目中可以替代树上的dsu算法,而且无论码量还是时间复杂度都是很优越的),以及LCT要使用的虚实树链剖分

所谓虚实树链剖分,就是讲一棵树的边划分为实边和虚边,对于实边连起来的一条链用一个SPLAY维护

举个例子:

这是一棵树(废话)

我们对他进行一下虚实剖分,如下:

如图,用实线表示的是实边,用虚线表示的是虚边,而我们用一些SPLAY维护这些链,就是:

 如图,用黑色实线连起来的点表示一个SPLAY,用黑色虚线框圈起来的是一个SPLAY,用蓝色虚线连的边表示父亲指针

为什么要这样建立SPLAY?

LCT维护树链的SPLAY有一个原则:每棵SPLAY的中序遍历会产生一个序列,而这个序列所对应的树链深度是单调递增的!

这是很重要的一个性质

接下来还有一个性质(当然在上面那张图上体现不太出来):就是每棵SPLAY的根节点的父亲一定要指向这棵SPLAY中中序遍历最靠前的那个点在原树中的父节点

换言之,我们不一定能保证SPLAY的根是深度最浅的,但是他的父亲一定要指向SPLAY维护的节点中深度最浅者的父节点

可是还要注意一点,如图,虽然存在9->4,10->4,8->4三个父亲指向,但是4的儿子只有一个,就是8,剩下的并不计入4的子节点中(即人们常说的“认父不认子”)

接下来我们就可以进行一些操作了:

首先,本身我们除了上述原则以外,如何进行虚实树链剖分是无所谓的,所以我们完全可以重构这些SPLAY,使得两个点被同一棵SPLAY维护

怎么操作?

我们使用一个函数叫access,使得一个点与整棵树的根所连接的

现在假设我们要把8号节点和1号节点放到一棵SPLAY里,我们怎么办呢?

首先,我们把8号节点转到本身SPLAY的根上

这一点很容易,根据SPLAY的伸展操作,双旋即可

不会旋转的详见前三篇SPLAY专题

(这里顺便说一句:由于在LCT中SPLAY只有一种旋转方式,就是将某一点旋转到SPLAY的根,所以旋转函数会比原来好写一些)

至于如何找到这个SPLAY的根,方法也很简单:一个SPLAY的根一定不会是任意一个SPLAY的儿子(废话),所以仅需找到这个点的父亲,看看这个点的父亲的儿子中有没有他就可以了,如果没有这个点就是根

剩下的旋转操作就和普通SPLAY一样了。

判断这个点是不是根:

bool berot(int rt)
{
if(c[f[rt]][0]==rt||c[f[rt]][1]==rt)
{
return 0;
}
return 1;
}

将某一点旋转到根的位置上:

void rotate(int rt)
{
int ltyp=0;
int fa=f[rt];
int ffa=f[fa];
if(c[fa][1]==rt)
{
ltyp=1;
}
if(!berot(fa))
{
if(c[ffa][1]==fa)
{
c[ffa][1]=rt;
}else
{
c[ffa][0]=rt;
}
}
c[fa][ltyp]=c[rt][ltyp^1];
c[rt][ltyp^1]=fa;
f[c[fa][ltyp]]=fa;
f[fa]=rt;
f[rt]=ffa;
update(fa);
}
void splay(int rt)
{
repush(rt);
while(!berot(rt))
{
int fa=f[rt];
int ffa=f[fa];
if(!berot(fa))
{
if((c[fa][0]==rt&&c[ffa][0]!=fa)||(c[fa][1]==rt&&c[ffa][1]!=fa))
{
rotate(rt);
}else
{
rotate(fa);
}
}
rotate(rt);
}
update(rt);
}

这是access中所需要的操作之一

接下来access操作就很简单了,因为现在我们已经让8号点到了根节点的位置上,如果用图来看,就是:

(所以说了那么多,其实我们只干了这么点事而已...)

接下来,我们就要把8扔进1所在的SPLAY里了

这就是access函数的第二步

首先,如果想把8扔进去,那么8就要在原来的SPLAY里面作为一个节点(废话)

作为哪个节点呢?

显然是右儿子!

为什么?

因为这个点的深度一定要比整个树链中深度最浅的点的父亲的深度深!(读十遍)

所以自然扔到右儿子去

什么?原来的右儿子怎么办?

不管...

反正LCT是“认父不认子”的...

access代码:

void access(int rt)
{
int y=0;
while(rt)
{
splay(rt);
c[rt][1]=y;
update(rt);
y=rt;
rt=f[rt];
}
}

所以,打通了以后,这个图会变成这样:

(忘记编号了,不过位置都没变,所以应该没啥事...)

(原谅我越来越丑陋的画风...)

这就完成了我们所需要的操作,接下来我们基本就可以“为所欲为”了

首先,我们可以对这棵树进行换根!

因为我们发现,在我们访问一条树链的时候,有很大概率这条树链本身并不能满足深度单调递增

那这样的树链是不能用同一棵SPLAY来维护的

可是我们就需要操作这个树链啊

那我们把树根换掉不就好了吗

所以假设我们需要操作树链(u,v),我们仅需将u转成树根,然后将v用access操作转进去就可以啦

于是问题变成了怎么把u转成树根

我们使用一个操作叫makeroot,表示把某一点变成树根

怎么变?直接拽上去?

你说对了...

首先,我们把u点用access操作转到和根节点在同一个SPLAY里

然后,我们把u点用SPLAY操作直接转到树根上

最后,我们翻转整个SPLAY即可

为什么?

首先,当我们access一个节点后,这个点一定是没有右子树的(很显然啊,access过程中把它置0了...)

那么这个点又被放在了原树的右节点上,那这不说明access后,这个节点在SPLAY中一定是深度最大的点吗?

那我们把他转到根节点上,再翻转整个SPLAY,不就让这个点成为了深度最大的点吗?(别忘了,LCT要求中序遍历深度单调递增啊)

这不就搞定了吗

所以操作如下:

void makeroot(int rt)
{
access(rt);
splay(rt);
reverse(rt);//翻转整个SPLAY
}

有了这个操作,剩下所有操作都是顺理成章的了

连边:如果想从(u,v)连一条边,那么我们可以首先把u转到树根上,然后直接连边即可

void link(int st,int ed)
{
makeroot(st);
if(getroot(ed)==st)
{
return;
}
f[st]=ed;
}

删边:把u转到树根上,直接删边即可

void cut(int st,int ed)
{
makeroot(ed);
if(getroot(st)==ed&&f[ed]==st&&!c[ed][1])
{
c[st][0]=f[ed]=0;
update(st);
}
}

抽出一条从u到v的树链:

void split(int st,int ed)
{
makeroot(st);
access(ed);
splay(ed);
}

剩下基本就随便搞了

贴下bzoj 2049代码

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define which(x) (c[f[x]][1]==x)
#define ls tree[rt].lson
#define rs tree[rt].rson
using namespace std;
int c[100005][2];
int f[100005];
bool ttag[100005];
char s[10];
bool berot[100005];
int n,m;
void reverse(int rt)
{
ttag[rt]^=1;
swap(c[rt][0],c[rt][1]);
}
void pushdown(int rt)
{
if(ttag[rt])
{
reverse(c[rt][0]);
reverse(c[rt][1]);
ttag[rt]=0;
}
}
void repush(int rt)
{
if(!berot[rt])
{
repush(f[rt]);
}
pushdown(rt);
}
void rotate(int rt)
{
int ltyp=0;
int fa=f[rt];
int ffa=f[fa];
if(c[fa][1]==rt)
{
ltyp=1;
}
if(berot[fa])
{
berot[fa]=0;
berot[rt]=1;
}else
{
if(c[ffa][1]==fa)
{
c[ffa][1]=rt;
}else
{
c[ffa][0]=rt;
}
}
c[fa][ltyp]=c[rt][ltyp^1];
c[rt][ltyp^1]=fa;
f[c[fa][ltyp]]=fa;
f[fa]=rt;
f[rt]=ffa;
}
void splay(int rt)
{
repush(rt);
while(!berot[rt])
{
int fa=f[rt];
int ffa=f[fa];
if(!berot[fa])
{
if((c[fa][0]==rt&&c[ffa][0]!=fa)||(c[fa][1]==rt&&c[ffa][1]!=fa))
{
rotate(rt);
}else
{
rotate(fa);
}
}
rotate(rt);
}
}
void access(int rt)
{
int y=0;
while(rt)
{
splay(rt);
berot[c[rt][1]]=1;
berot[y]=0;
c[rt][1]=y;
y=rt;
rt=f[rt];
}
}
void getroot(int rt)
{
access(rt);
splay(rt);
reverse(rt);
}
void link(int st,int ed)
{
getroot(st);
f[st]=ed;
}
void cut(int st,int ed)
{
getroot(st);
access(ed);
splay(st);
c[st][1]=f[ed]=0;
berot[ed]=1;
}
bool check(int st,int ed)
{
getroot(st);
access(ed);
splay(st);
while(!berot[ed])
{
ed=f[ed];
}
if(st==ed)
{
return 1;
}else
{
return 0;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
berot[i]=1;
}
for(int i=1;i<=m;i++)
{
scanf("%s",s);
int x,y;
scanf("%d%d",&x,&y);
if(s[0]=='C')
{
link(x,y);
}else if(s[0]=='Q')
{
if(check(x,y))
{
printf("Yes\n");
}else
{
printf("No\n");
}
}else
{
cut(x,y);
}
}
}

SPLAY,LCT学习笔记(四)的更多相关文章

  1. LCT 学习笔记

    LCT学习笔记 前言 自己定的学习计划看起来完不成了(两天没学东西,全在补题),决定赶快学点东西 于是就学LCT了 简介 Link/Cut Tree是一种数据结构,我们用它解决动态树问题 但是LCT不 ...

  2. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

  3. IOS学习笔记(四)之UITextField和UITextView控件学习

    IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...

  4. java之jvm学习笔记四(安全管理器)

    java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...

  5. Learning ROS for Robotics Programming Second Edition学习笔记(四) indigo devices

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  6. Typescript 学习笔记四:回忆ES5 中的类

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  7. ES6学习笔记<四> default、rest、Multi-line Strings

    default 参数默认值 在实际开发 有时需要给一些参数默认值. 在ES6之前一般都这么处理参数默认值 function add(val_1,val_2){ val_1 = val_1 || 10; ...

  8. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  9. python3.4学习笔记(四) 3.x和2.x的区别,持续更新

    python3.4学习笔记(四) 3.x和2.x的区别 在2.x中:print html,3.x中必须改成:print(html) import urllib2ImportError: No modu ...

  10. Go语言学习笔记四: 运算符

    Go语言学习笔记四: 运算符 这章知识好无聊呀,本来想跨过去,但没准有初学者要学,还是写写吧. 运算符种类 与你预期的一样,Go的特点就是啥都有,爱用哪个用哪个,所以市面上的运算符基本都有. 算术运算 ...

随机推荐

  1. myisamchk命令修复表操作

    myisamchk命令使用总结 myisamchk实用程序可以用来获得有关你的数据库表的统计信息或检查.修复.优化他们 1.常用于myisamchk的检查选项--information, -i打印所检 ...

  2. Linux命令之touch

    touch命令 用处:新建文件 (默认是文本,你可以自己加后缀) 用法:在终端中输入touch加上要新建的文件的名字(注意!同一目录下文件夹和文件是不允许同名的) 示例: (新建一个名字叫newfil ...

  3. Linux命令之man

    man命令 用处:就是一个文档帮助手册 用法:在终端中输入man加上你想知道的命令,按Q退出man命令 示例: (我想知道pwd的用法) (我想知道ls命令的用法)

  4. unity2D动画和图片切割

    视频地址:   http://www.tudou.com/listplay/siFwDsllSEM.html 恩,我得到了素材,是这样的 这是一整张的图片,png格式的.很明显,这是类似于一个帧动画之 ...

  5. WebLogic 中的基本概念【转】

    完全引用自: WebLogic 中的基本概念 WebLogic 中的基本概念 上周参加了单位组织的WebLogic培训,为了便于自己记忆,培训后,整理梳理了一些WebLogic的资料,会陆续的发出来, ...

  6. Mybatis 学习总结

    1 Mybatis入门 1.1 单独使用jdbc编程问题总结 1.1.1 jdbc程序 public static void main(String[] args) { Connection conn ...

  7. 10 SpringBoot全面接管SpringMVC

    Spring Boot官方文档描述 If you want to keep Spring Boot MVC features and you want to add additional MVC co ...

  8. 七、UART

    7.1 介绍 UART(Universal Asynchronous Receiver Transmitter),通用异步收发器,用来传输穿行数据时 UART 之间以全双工方式传输数据,连线方法只有 ...

  9. 四、u-boot 链接脚本

    4.1 C语言中的段 编译器在编译程序的时候,将程序中的所有的元素分成了一些组成部分,各部分构成一个段,所以说段是可执行程序的组成部分. 代码段:代码段就是程序中的可执行部分,直观理解代码段就是函数堆 ...

  10. python的安装和pycharm的安装

    下载地址   官网:https://www.python.org/downloads/release/python-372/ Window 平台安装 Python: Add python xx to ...