LCT——Link Cut Tree及其应用

我们知道,想要维护树上的一段区间,我们可以采用重链剖分来将其划分

但是,树链剖分只能够维护静态(树的形态不发生变化)的树,倘若我们需要动态对树的形态进行修改,比如将某个结点换为树的根、树中边的增删、子树合并和分离操作等,并需要在线地回答相关询问,那么每一次修改后,轻重链都需要重构,效率就会大大降低

所以,我们需要一种数据结构能够动态地维护树上的区间

这就是今天要用到的LCT

LCT是在1982年由Tarjan大佬等人提出的

LCT原理:

前提概念:

splay树:

一种通过splay操作来维护的平衡树

splay(u)可以将u结点旋转成为这棵平衡树的根

实链:

对于结点u而言,我们任意地选取一个儿子v,那么连接u与v的边就称作实边

实边的特点是儿子能够访问父亲,父亲也能够访问儿子,即:tr[u].s[1]或tr[u].s[0]==v且tr[v].p==u

全部由实边构成的链叫做实链

虚链:

对于结点u而言,除了一条实边外,其他连接儿子的边均为虚边

虚边的特点是儿子能够访问父亲,父亲不能够访问儿子,即:tr[v].p==u且tr[u].s[1]!=v&&tr[u].s[0]!=v

实现原理:

为了配合树的形态变化,我们定义LCT作为辅助树,这棵树是怎么得到的呢?

我们对原树进行虚实链剖分,且令每一条实链都成为一颗splay树

这样,所有splay树就由虚边连接在一起,构成一个森林

性质:

1.每棵splay树都维护着一条按原树深度严格递增的实链(不会出现深度相同的节点)

2.每个结点都被包含,且仅被包含在一棵splay树中

3.虚边用伸展树的根来维护

4.LCT的实链所对应的伸展树是动态变化的,虚实边也是可以动态变化的,虚边与实边动态转化

5.无论如何虚实变化旋转,所有节点的相对位置都不变,如果原树路径(x,y)中没有z节点,那么操作完后,路径(x,y)也不会出现z结点

LCT的操作函数:

这里要区分splay树的根和原树的根

LCT总共有7种基本操作:

access(x)——在x节点到原树根之间打通一条实链

makeroot(x)——把x结点变为原树的根节点

findroot(x)——找到x所在的原树的根节点,并把原树的根节点旋转成他所在的splay树的根

split(x,y)——在x到y之间的路径建立一棵splay树,这棵树的根节点为y

link(x,y)——如果x和y之间不连通,加入一条边连接x和y

cut(x,y)——如果x和y连通,剪断这条边

isroot(x)——判断是否为所在的splay树的根

实现代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1E5 + 10;int n, m;
struct node{int s[2], p, v;int sum;//本次的sum是异或和int tag;//翻转懒标记
} tr[N];
int stk[N];//翻转左右子树
void reverse(int x){swap(tr[x].s[0], tr[x].s[1]);tr[x].tag ^= 1;
}//由下往上更新结点信息
void pushup(int x){tr[x].sum = tr[tr[x].s[0]].sum ^ tr[tr[x].s[1]].sum ^ tr[x].v;
}//由上往下传递懒标记翻转节点
void pushdown(int x){if(tr[x].tag){reverse(tr[x].s[0]);reverse(tr[x].s[1]);tr[x].tag = 0;}
}//splay树根节点的特点:
//与父亲为虚边连接
//如果父亲的左右儿子都不是自己
//说明连接x与父亲的边为虚边
//说明x是splay树的根节点
bool isroot(int x){return tr[tr[x].p].s[0] != x && tr[tr[x].p].s[1] != x;
}//比起普通的rotate,多了一个判断是否为splay树的根的操作
//因为需要确定更新父子关系的方式
void rotate(int x){int y = tr[x].p;int z = tr[y].p;int k = tr[y].s[0] == x;//把x转到y的位置if(!isroot(y)) tr[z].s[tr[z].s[1] == y] = x;//如果y不是splay的根tr[x].p = z;                                //那么z与x用实边连接//否则z与x用虚边连接//把x的异儿子转到x的位置tr[y].s[k ^ 1] = tr[x].s[k];tr[tr[x].s[k]].p = y;//把y转到x的异儿子的位置tr[x].s[k] = y;tr[y].p = x;//更新节点信息pushup(x);pushup(y);
}//把x旋转成splay树的根
void splay(int x){//在splay之前要把旋转会经过的路径上的点的tag全部下放int top = 0, r = x;stk[++top] = r;//将到根节点的路径上的点全部进栈while(!isroot(r)) stk[++top] = r = tr[r].p;//出栈pushdownwhile (top) pushdown(stk[top--]);while(!isroot(x)){int y = tr[x].p, z = tr[y].p;//当y不是splay树的根的时候//做双旋//等价于普通splay树的z!=kif(!isroot(y)){//直转中,折转底(tr[y].s[0] == x) ^ (tr[z].s[0] == y) ? rotate(x) : rotate(y);}rotate(x);//否则做单旋}
}//打通一条x到原树根的实链
//同时将x转成splay树的根
void access(int x){//留存x编号int z = x;//y为上一棵splay树的根节点,显然初始化为0并不影响//x为当前一棵splay树的根节点// 主要过程:// splay树的中序遍历就是结点按深度由小到大的排序// 由于我们需要打通一条x到原树的实链//那么上一棵splay树y的深度一定都比x的深度要大//所以我们将其接到x树的根节点的右子树上//然后原本x连接右子树的边就要变成虚边,也就是断开父可以访问子的这条边//然后x再通过虚边跳到他的父亲上面去//这样不断迭代,直到迭代到x跳到含有原树根的splay树上for (int y = 0; x; y = x,x=tr[x].p){//每次循环,先把x旋转成它锁在的splay树的根节点//然后把y接到x的右子树上//再更新x//然后x跳到x的父亲那里(通过虚边)//y变成xsplay(x);tr[x].s[1] = y;pushup(x);}//经过这样连接,splay树会变成一条链,我们再旋转x,减少树高splay(z);
}//把x变为原树根
void makeroot(int x){//首先打通一条x到原树根的实链access(x);//在这条实链中,x是深度最大的节点,原树根是深度最小的节点//同时x为这棵splay树的根//因此只需要翻转左右子树,把中序遍历顺序颠倒//x就变为了深度最小的节点,既是splay树的根,也是原树根reverse(x);
}int findroot(int x){//先打通一条x到原树根的实链access(x);//这个时候x是splay树的根//当x的左子树存在时//先下放懒标记//在跳到左子树while(tr[x].s[0]){pushdown(x);x = tr[x].s[0];}//跳到最后左子树不存在时,//就是跳到了中序遍历第一个,也就是深度最小的结点,也就是原树根节点//把原树根转成splay树的根splay(x);return x;
}//把路径(x,y)创建为一棵splay树
void split(int x,int y){//先把x变成原树根makeroot(x);//然后打通y与原树根的实链,也就是打通y到x的实链//并把y转成splay树的根access(y);
}//如果x不连通,就加一条边连接x与y
void link(int x,int y){//先把x变成原树根makeroot(x);//如果y的原树根不是x,说明x与y不连通//把y连成x的父亲//用虚边连接//这样不用改动其他任何边if(findroot(y)!=x){tr[x].p = y;}
}void cut(int x,int y){//先把x变为原树根makeroot(x);//如果x是y的原树根//说明x和y经过若干个点连通//如果y的父亲是x且y没有左子树//说明x和y的深度差为1,x是y的前驱,y是x的后继if(findroot(y)==x&&tr[y].p==x&&!tr[y].s[0]){tr[x].s[1] = tr[y].p = 0;pushup(x);}
}int main(){cin >> n >> m;for (int i = 1; i <= n;i++){cin >> tr[i].v;}int t, x, y;while(m--){cin >> t >> x >> y;if(t==0){split(x, y);cout << tr[y].sum << endl;}else if(t==1){link(x, y);}else if(t==2){cut(x, y);}else{splay(x);tr[x].v = y;pushup(x);}}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/37822.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

50-3 内网信息收集 - 域环境搭建

搭建准备: 在搭建准备阶段,我们需要准备三台 Windows 虚拟机:Windows Server 2012、Windows 7 和 Windows Server 2008。接下来,我们将配置 Windows Server 2012 作为域控制器,而 Windows 7 和 Windows Server 2008 将作为成员机加入域。建议保持这三台虚拟机的内存不超过…

51单片机第15步_串口多机通讯使用CRC8校验

本章重点介绍串口多机通讯使用CRC8校验。 数据格式:"$123xxxx*crc8\r\n"; 如:"$1234567890ABCDEF*06\r\n" 如:"$1231234567890*31\r\n" crc8是CRC校验值,为十六进制的ASCII码,不包含$和校验值前面的那个* #include <REG51.h> //包含…

王佩丰 Excel 基础二十四讲——目录

前言 跟着B站学习王佩丰 Excel 基础教程&#xff0c;本文章为索引目录 课程传送门&#xff1a;视频地址——点击前往 王佩丰Excel基础教程24讲完整版 第一讲&#xff1a;认识 Excel 第二讲&#xff1a;Excel 单元格格式设置&#xff08;未编辑&#xff09; 第三讲&#xff1a;…

Zoom屏幕共享:远程协作的桥梁

标题&#xff1a;Zoom屏幕共享&#xff1a;远程协作的桥梁 摘要 Zoom的屏幕共享功能是其最受欢迎的特性之一&#xff0c;它允许用户在视频会议中共享他们的屏幕内容&#xff0c;从而实现高效的远程协作和演示。本文将详细解释Zoom屏幕共享功能的工作原理&#xff0c;并提供使…

JavaScript 动态网页实例 —— 视频动画

要在网页中播放各种视频和音频文件,需要为页面添加插件和控件。ActiveX是Microsof公司的对象组件技术,允许Windows程序在运行时刻载入并使用其他程序。ActiveX控件常用作浏览器的子程序,以增强页面的交互作用。另外,尽管没有官方的HTML描述,但通常都使用<embed></…

unity中off mesh link组件无法正常使用

unity中off mesh link组件无法正常使用 问题解决 问题 如果使用了新版导航方式&#xff0c;发现只有当agent设置为humanoid才可以正常实现off mesh link的跳转效果&#xff0c;设置为其他agent type就无法正常跳转&#xff0c;且bake之后会发现off mesh link周围是没有圆圈的。…

Spring基础知识 - IOC、DI、AOP

1、什么是 Spring 框架&#xff0c;它的优点是什么&#xff1f;它的主要功能是什么&#xff1f; Spring 框架是一个开源的 Java 框架&#xff0c;主要用于开发企业级 Java 应用程序。它提供了一组强大的功能和工具&#xff0c;使得开发者能够更加容易地构建高效、可维护和可扩展…

(漏洞检查项) | 任意文件包含漏洞 file-include

(漏洞检查项)|任意文件包含漏洞 file-include 漏洞场景 1.含有动态包含语句 2.有类似于文件读取的url 漏洞描述 攻击者可以利用任意文件包含漏洞&#xff0c;读取任意文件&#xff0c;对服务器造成危害。 程序开发人员为了代码的灵活性&#xff0c;常常会将包含文件的路径…

influxdb时序数据库使用

influxdb时序数据库使用 1.1.免费无云influx申请1.2.Telegraf安装1.3.influxdb安装mac安装Redhat && Centos安装docker安装Kubernetes安装windows安装 1.4.influx CLI 安装1.5.influx命令行界面1.5.influx配置项权限认证配置管理 API 令牌 InfluxDB 是一个开源分布式时…

用通俗易懂方式讲解:快速部署大模型 ChatGLM3 并进行推理

在深入了解了一些大模型的知识之后&#xff0c;最好的方法是亲自动手搭建一个开源的大模型&#xff0c;以更深入地理解其工作原理。 在此基础上&#xff0c;我们将以 ChatGLM3 为例进行部署及推理&#xff0c;从而进一步探索大模型的应用和实践。 ChatGLM3简介&#xff1a; …

Alibaba Cloud Linux详解_操作系统兼容性_alinux稳定性全解析

Alibaba Cloud Linux是阿里云自研的稳定、安全、高性能的服务器Linux操作系统&#xff0c;完全兼容CentOS/RHEL生态和操作方式&#xff0c;又阿里云提供免费提供长期支持和维护LTS。Alibaba Cloud Linux是目前阿里云服务器最大规模使用的操作系统之一&#xff0c;可部署在Web网…

无刷直流电机(BLDCM)位置识别SVPWM控制

无刷直流电机&#xff0c;即BLDCM&#xff0c;在各个行业应用非常广泛。我们最熟悉的是在四轴飞行器中的应用&#xff0c;其中的电机基本都是BLDCM。除此之外&#xff0c;汽车电子、家用电器、航空航天、办公自动化、机器人等领域都有重要应用。 梯形波/方波无刷直流电机被称为…

基于单片机技术的按键扫描电路分析

摘 要&#xff1a; 单片机应用技术被广泛应用于各种智能控制系统中&#xff0c;是电子信息类专业学生必修的一门专业课。在单片机端口信息输入模块中&#xff0c;按键是主要元器件之一&#xff0c;笔者主要介绍矩阵键盘的电路设计及控制程序编写&#xff0c;分析了单片机端口连…

asyncawait

参考&#xff1a; 并发&#xff1a;并发基本概念 python 使用 async 和 await 语法的东西来写”异步代码“。 import asyncio import httpxasync def fetch_baidu():url "https://www.baidu.com"async with httpx.AsyncClient() as client:try:response await c…

使用Java Executors框架处理并发任务

一、并发与Java Executors框架简介 一、并发编程的重要性 并发编程是现代编程中最重要的概念之一。在更多的核心和更快的处理器出现的今天,如何充分利用这些资源就变得异常重要。并发编程允许你的程序同时处理多个任务,从而使程序更有效地利用系统资源,提高执行效率。 提…

Oracle给用户单个表查询权限

Oracle给用户单个表查询权限 1. 创建用户 --创建用户thfj_test,密码为thfj_test create user thfj_test identified by thfj_test;2. 用户授权 --授权连接数据库权限给thfj_test grant create session to thfj_test; --授权查询表USER_INFO 的权限给thfj_test grant sele…

python-20-零基础自学python-用类和while设计一个掷多次、多面骰子的工具的基础

学习内容&#xff1a;《python编程&#xff1a;从入门到实践》第二版 知识点&#xff1a;类、random、while循环、把while循环和类结合起来 练习内容&#xff1a; 练习9-13&#xff1a;骰子 创建一个Die类&#xff0c;它包含一个名为sides的属性&#xff0c;该属性的默认值…

汽车电子行业知识:什么是电子后视镜

文章目录 1.什么是电子后视镜2.有哪些汽车用到了电子后视镜3.电子后视镜的原理及算法4.电子后视镜的优点5.电子后视镜的未来市场将继续增长 1.什么是电子后视镜 电子后视镜是一种集成了电子元件和显示屏的汽车后视镜&#xff0c;用于替代传统的机械后视镜。它通过内置的摄像头捕…

C++期末练习

1. 多态 要求 动态多态性 题目描述 定义一个抽象类shape&#xff0c;用于代表几何图形&#xff0c;设置计算几何图形体积的外部接口&#xff1b;由shape类派生出圆柱类cylinder、球sphere&#xff1b;圆柱体类型有私有数据成员半径r&#xff0c;高h&#xff1b;球类有私有数…

外星人存在的观点

以下是一些具体的证据来支持外星人存在的观点&#xff1a; 一、宇宙中的生命适宜条件 行星多样性&#xff1a;宇宙中存在着数以亿计的恒星和行星&#xff0c;其中许多行星位于恒星宜居带内&#xff0c;拥有适宜的温度和液态水等生命必需条件。例如&#xff0c;开普勒-452b行星…