首页 > 其他分享 >LCT的简陋总结

LCT的简陋总结

时间:2023-09-22 20:44:47浏览次数:45  
标签:总结 splay LCT ch int 简陋 void Splay

不想了解基础知识的可以直接从 \(LCT\) 基础操作部分开始,前面不是很重要

目录

主要参考oi-wiki

\(LCT\)基础知识

树上操作是算法竞赛中重要的操作

由于树的特殊性,使得维护一些子树信息和路径信息变得较为困难,

常见情况有如下几种:

  • 修改两点间路径权值。
  • 查询两点间路径权值和。
  • 修改某点子树权值。
  • 查询某点子树权值和。
  • 断开并连接一些边,保证仍是一棵树。

常见的做法是将树线性化,即树链剖分。本蒟蒻至今没完全明白这玩意对于 \(LCT\) 来说并不是我们常见的重链剖分,也不是长链剖分,而是一种只会用在这里的新的剖分方式

但是前面学习的树链剖分只能解决静态树上的问题,如果树的形态发生了改变则无法完成,

这时候需要动态树,\(LCT(Link-Cut \ \ Tree)\) 孕育而生,当然还有啥 \(ETT,Top \ Tree\) 这些动态树

\(LCT\) 也是将某一个儿子的连边划分为实边,而连向其他子树的边划分为虚边,这种剖分称之为实链剖分

实链剖分

我们可以任意选择一条通向儿子的边作为实边,其他的边为虚边,和别的剖分的区别在于虚实是可以动态变化的,因此要使用灵活的平衡树 \(Splay\) 来维护每一条由若干实边连接而成的实链。其中 \(Splay\) 也被称作辅助树。不会平衡树的同学请出门左转,话说不知道有没有别的平衡树做辅助树的。

辅助树

这里的每一棵 \(Splay\) 其实都是维护的一条原树上的实链。\(Splay\) 的根节点的父亲节点指向原树上这条链的根节点。这类父亲链接与通常 Splay 的父亲链接区别在于儿子认父亲,而父亲不认儿子,对应原树的一条虚边我们不需要维护原树,可以通过辅助树还原出原树

一些性质

  • 原树中的实链 : 在辅助树中节点都在一棵 \(Splay\) 中。
  • 原树中的虚链 : 在辅助树中,子节点所在 \(Splay\) 的根节点指向父节点,但是父节点的两个儿子都不指向子节点。
  • 原树的根不等于辅助树的根。
  • 原树的父亲指向不等于辅助树的父亲指向。
  • 辅助树是可以在满足辅助树、\(Splay\) 的性质下任意换根的。
  • 虚实链变换可以轻松在辅助树上完成,这也就是实现了动态维护树链剖分。

\(LCT\) 维护的对象其实是一个平衡树森林。 在实链剖分的基础下,\(LCT\) 支持以下的操作:查询、修改链上的信息;随意指定原树的根(即换根);动态连边、删边;合并、分离树;动态维护连通性……欢迎各位dalao补充

又臭又长的部分结束了

\(LCT\) 基础操作

函数定义

\(LCT\)有以下的一些基本函数:

  1. \(Access(x)\):访问 \(x\) 结点,且将 \(x\) 到根节点的路径变为实链必须实现
  2. \(Findroot(x)\):找 \(x\) 所在树中编号最小的结点
  3. \(Rotate(x)\):反转 \(x\) 点到根的这条链, 将 \(x\) 变为根
  4. \(Makeroot(x)\):使 \(x\) 变为当前树的根
  5. \(Cut(x,y)\):将结点 \(y\) 与其所在树中的父亲结点 \(x\) 之间的边删去,即以 \(y\) 为根的子树删去
  6. \(Link(x,y)\):添加一条边 \((x,y)\),即 \(x\) 是 \(y\) 的父亲
  7. \(Split(x,y)\):提取出 \(x,y\) 间的路径
  8. \(Splay(x)\):平衡树的相关操作
  9. \(Isroot(x)\):判断 \(x\) 是否为其所在树的根,可能常写为 \(Nroot(x)\),确实要简单些

函数实现

\(pushdown() \ \And\And \ pushup()\) 自己根据题目实现

\(Nroot()\) 很简单,利用 \(Splay\) 树根的父节点儿子不指向当前节点的性质判断

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

\(Splay() \And\And Rotate()\) 其实就是平衡树上该咋写咋写,有点小改动

void rotate(int x){
	int y = f[x],z = f[y],k = ch[y][1]==x,w = ch[x][!k];
	if(nroot(y)) ch[z][ch[z][1]==y] = x;//注意这一句
	ch[x][!k] = y;
	ch[y][k] = w;
	if(w) f[w] = y;
	f[y] = x;
	f[x] = z;
	pushup(y);
}
void update(int p){
	if(nRoot(p)) update(f[p]);
	pushDown(p);
}
void splay(int x){
	int y = x,z = 0;
	st[++z] = y;
	while(nroot(y)) st[++z] = y = f[y];
	while(z) pushdown(st[z--]);
	//上面这部分有些人会单独写出来,就是update那段
	while(nroot(x)){
		y = f[x];
		z = f[y];
		if(nroot(y))
			rotate((ch[y][0]==x)^(ch[z][0]==y)?x:y);
		rotate(x);
	}
	pushup(x);
}

\(Access()\) 只有如下四步操作:

  1. 把当前节点转到根。
  2. 把儿子换成之前的节点。
  3. 更新当前点的信息。
  4. 把当前点换成当前点的父亲,继续操作。
void access(int x){
	for(int y = 0;x;x = f[y = x]){
		splay(x);
		rc = y;
		pushup(x);
	}
}

\(Makeroot()\) 其实也很好实现,只需要把当前点先 \(Access()\) 一边在用 \(Splay()\) 转至根节点

void pushr(int x){//按题目自己实现
	int t = lc;
	lc = rc;
	rc = t;
	r[x]^=1;
}
void makeroot(int x){
	access(x);
	splay(x);
	pushr(x);//这句自己看着办
}

\(Link()\) 记得判断是否是在同一棵树内

void link(int x,int y){
	makeroot(x);
	if(findroot(y)!=x) f[x] = y;
}
//oi-wiki上的写法,未附带判断
void Link(int x, int p) {
	makeRoot(x);
	splay(x);
	f[x] = p;
}

\(Split()\) 就是拿出一棵 Splay,维护 \(x\) 到 \(y\) 的路径。

可以直接把需要的路径拿出到 \(y\) 的子树上,可以进行其他操作。

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

\(Findroot()\) 查找的是原树的根,只用到前面说的辅助树中序遍历为原树上的链的性质便可以了,一直走左儿子到底就是根,\(Splay()\) 操作是来保证复杂度的

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

\(Cut()\) 也要记得判断是否合法,除非说了保证合法

需要满足以下条件:

  1. 二者连通
  2. 路径上没有其他的链
  3. \(x\) 没有右儿子
void cut(int x,int y){
	makeroot(x);
	if(findroot(y)==x&&f[y]==x&&!ch[y][0]){
		f[y] = ch[x][1] = 0;
		pushup(x);
	}
}

以下是洛谷模板题代码:

#include<bits/stdc++.h>
#define lc ch[x][0]
#define rc ch[x][1]
#define N 300010
using namespace std;
int n,m,f[N],ch[N][2],val[N],s[N],st[N];
bool r[N];
bool nroot(int x){
	return ch[f[x]][0]==x||ch[f[x]][1]==x;
}
void pushup(int x){
	s[x] = s[lc]^s[rc]^val[x];
}
void pushr(int x){
	int t = lc;
	lc = rc;
	rc = t;
	r[x]^=1;
}
void pushdown(int x){
	if(r[x]){
		if(lc) pushr(lc);
		if(rc) pushr(rc);
		r[x] = 0;
	}
}
void rotate(int x){
	int y = f[x],z = f[y],k = ch[y][1]==x,w = ch[x][!k];
	if(nroot(y)) ch[z][ch[z][1]==y] = x;
	ch[x][!k] = y;
	ch[y][k] = w;
	if(w) f[w] = y;
	f[y] = x;
	f[x] = z;
	pushup(y);
}
void splay(int x){
	int y = x,z = 0;
	st[++z] = y;
	while(nroot(y)) st[++z] = y = f[y];
	while(z) pushdown(st[z--]);
	while(nroot(x)){
		y = f[x];
		z = f[y];
		if(nroot(y))
			rotate((ch[y][0]==x)^(ch[z][0]==y)?x:y);
		rotate(x);
	}
	pushup(x);
}
void access(int x){
	for(int y = 0;x;x = f[y = x]){
		splay(x);
		rc = y;
		pushup(x);
	}
}
void makeroot(int x){
	access(x);
	splay(x);
	pushr(x);
}
int findroot(int x){
	access(x);
	splay(x);
	while(lc){
		pushdown(x);
		x = lc;
	}
	splay(x);
	return x;
}
void split(int x,int y){
	makeroot(x);
	access(y);
	splay(y);
}
void link(int x,int y){
	makeroot(x);
	if(findroot(y)!=x) f[x] = y;
}
void cut(int x,int y){
	makeroot(x);
	if(findroot(y)==x&&f[y]==x&&!ch[y][0]){
		f[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]);
	while(m--){
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		switch(op){
			case 0:
				split(x,y);
				printf("%d\n",s[y]);break;
			case 1:
				link(x,y);break;
			case 2:
				cut(x,y);break;
			case 3:
				splay(x);val[x] = y;break;
		}
	}
	return 0; 
}

完结撒花

以后再说补充习题的事

标签:总结,splay,LCT,ch,int,简陋,void,Splay
From: https://www.cnblogs.com/cztq/p/17723324.html

相关文章

  • 封装总结
    封装就是把public的类变成private的类(个人理解)露出该露的,封装该封装的定义一个私有类以后可以用ALT+INSERT快捷生成get和set方法get方法主要是为了拿到私有类set是为了设置私有类的值,以及在set方法中写一些判断条件,以防止出现一些不合理的值构造器,就是一个无参的一旦有了有......
  • 2023.9.22——每日总结
    学习所花时间(包括上课):9h代码量(行):0行博客量(篇):1篇今天,上午测试,下午做任务。我了解到的知识点:1.echarts结合mysql、javaweb实现大数据的可视化;明日计划:1.完成任务;2.尽力完成测试;......
  • [一些总结]php中的弱相等和强相等
    PHP中的弱相等和强相等相关知识网络上有太多人总结了,但还是想自己动动手写点东西加深加深印象,哈哈。先看下GPT对弱相等和强相等的解释:在PHP中,弱相等(==)和强相等(===)是用于比较两个值的操作符,它们有以下区别:1.弱相等(==):弱相等用于比较两个值是否相等,不考虑值的数据类型。如......
  • 考试程序语句总结
    1、导csv文件到hive数据库建表便于接收数据:createtabletest1(day_idvarchar(30),sale_nbrvarchar(30),buy_nbrvarchar(30),cntvarchar(30),roundvarchar(30))rowformatserde'org.apache.hadoop.hive.serde2.OpenCSVSerde'WITHSERDEPROPERTIES("separatorChar......
  • 数字和模拟后仿总结
    1      网表数字芯片设计一般将布局布线前的工作称之为数字前端(FrontEnd)设计,而将布局布线之后的工作称为数字后端(BackEnd)设计 Ø  按照芯片后端流程,门级网表主要分为综合网表,DFT网表,PR网表,其中PR网表是包含SDF的最终版网表。Ø  网表验证一般有三种形式:仿真......
  • dockerfile编写总结
    编写Dockerfile文件1.dockerfile结构介绍  from基础镜像  maintainer维护者信息  run命令前加run  CMD容器启动时执行的操作,可以自定义脚本,也可执行系统命令  ENTRYPOINT容器启动时执行的操作,设置指令指定容器启动时执行的命令,可以追加命......
  • 总结,知识的结构性
    一门程序设计语言的基本要素和技能可以概括为以下几点:语法和语义:每门语言都有自己的语法规则和语义理解,比如操作符的使用、变量的定义,如何创建和使用函数等。数据类型和数据结构:理解基本的数据类型(例如整数、浮点数、字符串等)和数据结构(例如数组、列表、字典、集合等)。控......
  • 基于Kubernetes的Serverless PaaS稳定性建设万字总结
    作者:许成铭(竞霄)数字经济的今天,云计算俨然已经作为基础设施融入到人们的日常生活中,稳定性作为云产品的基本要求,研发人员的技术底线,其不仅仅是文档里承诺的几个九的SLA数字,更是与客户切身利益乃至身家性命息息相关,稳定性压倒一切。本文将侧重于实际落地而非方法论,阐述云产品SAE......
  • 9.21 周四总结
    //生成1-100内的整数Randomr1=newRandom();intnum1=r1.nextInt(0,100);importjava.util.*;publicclassTestRandom{ publicstaticvoidmain(String[]args) { Randomrand=newRandom(); System.out.println("rand.nextBoolean():"+rand.nextBoo......
  • 3-Linux文档查看指令,关机重启、相关知识点的拓展与总结
    一、文档的查看指令1、tail指令作用:查看一个文件的末n行语法:#tail-n文件的路径说明:-n可以不写,不写,默认表示10行。案例:新建一个1.txt文档,使用tail指令查看root/1.txt文件的末5行和末10行tail-5/root/1.txttail/root/1.txt2、head指令作用:查看文件的头n行语法:#hea......