首页 > 其他分享 >LCT 学习笔记

LCT 学习笔记

时间:2023-12-14 19:45:09浏览次数:28  
标签:splay LCT ch 实链 int 笔记 学习 fa 节点

引子

在古老且美妙的数据结构王国,一次,一个巨大的怪兽出现在了这个国家,这个怪兽是一棵树,打败这个怪兽只需要能快速求出这个怪兽任意一条路径上的和就可以了,可是他灵活多变,自己的手脚可以调换位置,或拿下来(边可以断掉或连上)身上的每一寸肌肤都可改变其硬度(点可以修改值)

树链剖分找到了$splay$,他们决定联手,树链剖分将这个怪兽分成一条条的链,让$splay$去套在链上,无论怪兽怎样变化,$splay$都可以去维护这条链,怪兽就这样被打败了

这是怎么做到的呢??

$aqz180321$ 今天比较疯狂,决定研究一下

前置芝士

splay

主要用到他的翻转区间和将任意一个节点旋转到根的功能

树链剖分

了解思想就可以

正题

传送门

我们先对其进行实链剖分


对于一个点连向它所有儿子的边,我们自己任意选择一条边进行剖分,我们称被选择的边为实边,其他边则为虚边。

对于实边,我们称它所连接的儿子为实儿子。对于一条由实边组成的链,我们同样称之为实链。

请记住我们选择实链剖分的最重要的原因:它是我们选择的,灵活且可变。正是它的这种灵活可变性,我们采用 $Splay Tree$ 来维护这些实链。

$--$ oi-wiki


实链有 :

  • $A-B-C$

  • $C-E$

  • $F$

我们对于每一颗实链都建一棵$splay$树

$splay$树中通过深度进行的划分,也就是说 $lson$的深度 $<$ 当前节点的深度 $<$ $rson$ 的深度

不难发现,这颗$splay$树的中序遍历的结果,相当于从上到下遍历这条链

然后将这些$splay$树的根指向在原树中 这条链 的父亲节点(即链最顶端的点(即深度最小的点)的父亲节点)

如图

我们将这样构成的树叫做辅助树

然后这就是他的初步,是不是感觉挺简单的,他的神通之处是在他的函数

只有几个函数而已

函数

前提:变量名

$ch[x][0]$ 左儿子(对于splay中)

$ch[x][1]$ 右儿子(对于splay中)

$fa[x]$ 父亲(对于辅助树中)

因为对于的树不同,所以会出现一种情况:

它的辅助树可能长这样:

其中$fa[D] = B$,但是$ch[B][0] = A$ $ch[B][1] = E$均不是$D$

说人话就是,任何儿子认父亲,但是父亲只认实儿子(因为实儿子和父亲在一条实链中即在一棵$splay$中)

所以可以根据这个特性去判断当前节点是否为$splay$的根

bool isroot (int x) {
	return x != ch[fa[x]][0] && x != ch[fa[x]][1];
}

$tag[x]$ 翻转标记,同文艺平衡树中的区间翻转操作中的标记

实链剖分的变量:

$sum[x]$ 链的和

$val[x]$ 当前节点的值

isroot

上文说过了

pushup

链维护和的函数

void pushup (int x) {
	sum[x] = sum[ch[x][0]] ^ sum[ch[x][1]] ^ val[x];
}

模板题中是异或和,所以这里是异或

reverse

翻转,同文艺平衡树

void reverse (int x) {
	std::swap(ch[x][0], ch[x][1]);
	tag[x] ^= 1;
}

pushdown

下放翻转标记,同文艺平衡树

void pushdown (int x) {
	if (tag[x]) {
		if (ch[x][0]) reverse(ch[x][0]);
		if (ch[x][1]) reverse(ch[x][1]);
		tag[x] = 0;
	}
}

update

从当前节点一直到根节点递归下放翻转标记

void update (int x) {
	if (!isroot(x)) update(fa[x]);
	pushdown(x);
}

rotate

$spaly$中的旋转

void rotate (int x) {
	int y = fa[x], z = fa[y], kth = get(x);
	if (!isroot(y)) ch[z][y == ch[z][1]] = x;//特别注意的地方
	ch[y][kth] = ch[x][kth ^ 1];
	fa[ch[x][kth ^ 1]] = y;
	ch[x][kth ^ 1] = y;
	fa[y] = x;fa[x] = z;
	pushup(y), pushup(x);
}

旋转一定不能超过当前的$splay$即超过根

splay

$spaly$中的旋转到根

void splay (int x) {
	update(x);//释放旋转标记
	for (int f; f = fa[x], !isroot(x); rotate(x))
		if (!isroot(f)) rotate(get(f) == get(x) ? f : x);
       //注意不要超出根
}

是不是挺简单的

接下来是LCT的精华,笔者已经冲疯了

access

将当前节点到根连一条实链,实链使我们规定的链,所以会很灵活

void access (int x) {
	for (int p = 0; x; p = x, x = fa[x]) {
		splay(x);ch[x][1] = p;pushup(x);
	}
} 

什么,这么简单???但是很抽象

可以见$oi-wiki$的演示

传送门

这里就文字描述一下

从根到一个节点路径应该是唯一的,深度也是递增的

首先将$x$旋转到根,这是$ch[x][0]$就是深度比他小的,这些是要的,因为是从根到当前节点,这些节点肯定经过,所以$ch[x][1]$就可以不要了故可以将$ch[x][1] = p$ ,$pushup$修改一下 ,然后$p = x, x = fa[x]$去到他的父亲,父亲也是深度比他大的,所以也要,但是父亲所在的$splay$中有不合法的,所以去搞他的父亲,搞完之后,将自己没有用的$ch[x][1]$连上,这颗平衡树深度大小关系也是满足的

笔者能力有限,只能描述成这样

它执行以后,辅助树改变了,但是原树没有改变,只是改变了原树的实链的划分

makeroot

成为根,原树是一棵无根树,所以根可以是任意一个

先从根节点到要成为根的节点变成实链,在将当前节点旋到最上面,因为当前节点是深度最小的,所有右儿子为空,翻转儿子,左儿子为空,就成为了深度最小的点,即根

void makeroot (int x) {
	access(x);splay(x);
	reverse(x);
}

findroot

找辅助树的根,先把根节点到要找根的节点变成实链,再将当前节点旋转到上边,最后一直向左找,最左边就是深度最小的,即是根

最后在把根旋转上来,方便以后操作

int findroot (int x) {
	access(x);splay(x);
	while (ch[x][0]) pushdown(x), x = ch[x][0];
	splay(x);
	return x;
}

split

将x到y的路径变成实链

先把x变成根,在从根向y连一条实链就可以了,最后将y旋转上去,方便以后查询

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

将x和y连边,这就会对原树造成影响了与上边的split有很大的区别

先将x变成根,再看看y是否和x在一个辅助树内(即是否在一个原树内),如果没有,此时x是根,父亲是空的,就可以将父亲指向y

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

cut

将x和y的边删除

首先将x变成根,判断x和y在原树中是否是父子关系

注意!if语句中的findroot函数干了很多事情,首先x和y成为了一条链(因为x此时是根)x又回到了所在splay树的根节点

上文我们说过,splay树的中序便利就是原树从上到下的便利,所以x和y在原树是父子关系,他们的便利是紧挨着的,所以判断$fa[y] == x $ && $ !ch[y][0]$ 才能保证中序遍历是紧挨着的(先便利左子树,在输出当前,在便利右子树)

最后就双向奔赴,fa和ch全部清零就可以了

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

主函数

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%d", val + i);
	for (int i = 1, op, x, y; i <= m; i++) {
		scanf("%d%d%d", &op, &x, &y);
		switch (op) {
			case 0: split(x, y);printf("%d\n", sum[y]);break;
			case 1: link(x, y);break;
			case 2: cut(x, y);break;
			case 3: splay(x);val[x] = y;break;
		}
	} 
	return 0;
}

查询,就先将x到y连一条链,因为split的最后y变成了根,所以直接输出y就可以了

修改,需要先将x变成根,他的修改才不会影响任何人

大功告成

在LCT的相关题目中,要充分利用懒标记,去想办法维护我们要求的值,注意细节,思考要不要执行pushup...update...pushdown...等操作

标签:splay,LCT,ch,实链,int,笔记,学习,fa,节点
From: https://www.cnblogs.com/aqz180321/p/17901861.html

相关文章

  • 2023-2024-1学期20232423《网络空间安全导论》第六周学习总结
    教材学习——应用安全基础应用安全概述云计算造成了数据所有权和管理权的分离,在以下两方面开展持续研究:云计算基础设施的可信性、云数据安全保障。工业互联网:形成跨设备、跨系统、跨厂区、跨地区的互联互通,推动整个制造服务体系智能化。数据汇集到云端,要保证系统的可靠运行,......
  • 2023-2024-1 20232312 《网络空间安全导论》第六周学习
    2023-2024-120232312《网络空间安全导论》第六周学习教材学习内容总结6.1应用安全概述应用安全情况概述:在各类应用服务系统重,身份认证是保障应用安全的基础,其不仅仅包括传统的人的身份认证、软件等网络实体都需要身份认证和可信管理。不同场景、不同约束条件下都需要采用......
  • 【机器学习】算法作用与依赖库合集
    算法与库1.决策树:-库: fromsklearn.treeimportDecisionTreeClassifier(分类树) fromsklearn.treeimportDecisionTreeRegressor(回归树)-计算场景:分类和回归问题2.逻辑回归:-库: fromsklearn.linear_modelimportLogisticRegression-......
  • 秦疆的Java课程笔记:69 面向对象 Super详解
    super调用父类属性//首先写一个父类publicclassPerson{protectedStringname="1";}//然后写一个子类publicclassStudentextendsPerson{privateStringname="2";publicvoidtest(Stringname){System.out.println(name)......
  • 秦疆的Java课程笔记:70 面向对象 方法重写
    重写都是方法的重写,和属性没有关系。//父类写一个静态方法======================publicclassA{publicstaticvoidtest(){System.out.println("A=>test()");}}//子类也写一个静态方法====================publicclassBextendsA{......
  • CRON表达式学习
    一、什么是CRON表达式1.1介绍CRON表达式概念CRON表达式是一种时间表达式,用于指定定期执行任务的时间规则。它可以被用来执行非常基本的任务,例如从数据库备份到每天自动发送电子邮件。1.2CRON表达式的由来CRON表达式最初是在UNIX和类似的操作系统中创建的。名称“CRON”代表......
  • C++学习笔记十一:数据类型的转换
    一个表达式里的所有变量应该具有相同的类型。上溢和下溢(overflowandunderflow):1.隐式转换(implicitly):编译器自动进行。总是把占用内存小的数据类型转化为占用大的数据类型。int类型转换为doubledoubleprice{45.6};intunits{10};autototal_price=price*un......
  • 01_前言和学习方法介绍
    01_前言和学习方法介绍ARM裸机程序系统结构图应用层驱动层硬件层类Android等复杂功能系统结构图(有OS)ApplicationsKernelDriverH/W学习内容交叉编译环境搭建bootloader功能子系统内核核心子系统文件系统子系统学习思路和方法先整体后局部,层层推进如......
  • 01_ARM学习准备工作
    01_ARM学习准备工作熟悉Tiny210开发ARM9-2410ARM11-6410CortexA8-Tiny210CortexA15...1.开始进入到真正的嵌入式阶段1.1.理解一下我们要学的内容启动过程1、上电2、从BIOS里读引导信息3、bootloader:准备运行环境,引导操作系统3、操作系统kernerlinit4......
  • 【项目学习】谷粒商城学习记录7 - 认证服务
    【项目学习】谷粒商城学习记录7-认证服务一、环境搭建&准备工作1.创建新模块2.配置依赖pom.xml文件引入common模块,排除gulimall-common包的mybatis-plus将模块添加到注册中心添加配置信息添加服务发现注解启动类添加远程调用注解@EnableFeignClients测......