P3690 【模板】动态树(LCT)

news/2025/1/24 16:20:18/文章来源:https://www.cnblogs.com/LG017/p/18689711

P3690 【模板】动态树(LCT)

闲话:

余既知 LCT ,后半日,吾志学之。时至机房,广查博客,或苦思冥想。怎料实力不济,铩羽而归。他人问之:“闻汝知 LCT ,且何谓 LCT 也”。其后半日,吾弃之,树坏不修。其后半年,余久摆烂无聊,乃复修LCT,其成稍进于前。然自后余多爱线段树,不常写。

blog 有 LCT 树,吾集训之年所首知也,今已亭亭如盖矣。

人话版:

暑假时第一次知道了 LCT 奈何蒟蒻太弱,三看题解而不懂。然后就搁置了大半年,然后在 2024/12/23 靠着神秘的题解之力 A 了这题,但还是一道 LCT 除模板以外的题都没写,直至今日,再战LCT,复修总结。

Solution:

首先是我最赞扬的一篇题解,要看图的话可以参考,这篇题解的图真的很好。

splay的部分这里先不过多叙述,我们先把它当成普通的splay就好。支持两种操作

  • 1 rotate :将某个节点上旋
  • 2 splay :将某个节点不断上旋,使其成为该平衡树的根。

首先我们明确一下,LCT 维护的是很多颗平衡树,每一颗平衡树对应的是一个联通块,平衡树上的键值是在原图上该节点的深度

首先介绍一下最重要的操作:

access

表示将当前节点xx所在连通块(平衡树)的根root 这段路上的所有节点(在原树上)拿出来建一颗平衡树。

虽然说是“拿出来”,但是我们实际的操作是将从 x 开始不断跳父亲,让后将当前节点所对应平衡树节点 \(x\) 设为 其父亲 \(fa\) 的右儿子。我们思考为什么这么做是可行的,前面说到:平衡树上的键值是在原图上该节点的深度。所以在合并操作进行到 \(fa\) 时,并没有比深度更大的节点,所以 \(fa\) 的左儿子其实是空的。所以我们只要将之前合并好的连通块作为 \(fa\) 的右儿子就好了。这样就保证了在进行完 access 操作之后,\(x\) , \(root\) 在同一平衡树内并且这颗平衡树有且仅有 x->root 这跳路径上的所有节点。

形式化的: $$splay=$$ \({\) $$y|y \in(x->root)$$ \(}\)

说了那么多,其实代码很简单:

void access(int x){int y=0;while(x){//每次 splay 完,x 是没有左子树的,因为在目前合并出来的这颗 平衡树上,x 在原树中的深度最小。//而它的右儿子又被我们强行赋为了 rt->x 这条链所形成的平衡树//这样就保证了在 access 完了之后,这颗平衡树维护只存在 x->root 这段路径上的点splay(x);rs=y;pushup(x);y=x;x=fa;}}

有了这个操作剩下的操作就好理解了。

make_root:

使得 x 成为当前平衡树的根。注意是成为而非上旋至。这两个操作有着本质上的区别。make_root会直接把它在原树上的深度直接赋为当前平衡树(连通块)内最小的,使得该节点在平衡树和原树都是该平衡树(连通块)真正意义上的根。

听起来貌似挺麻烦的,但其实非常好写。

inline void rev(int x)
{swap(t[x].ch[0],t[x].ch[1]);t[x].tag^=1;
}
void make_root(int x)//换根
{access(x);splay(x);rev(x);
}

为什么可以这么写:因为在 access(x),splay(x) 执行完了之后 \(x\) 是根而且没有右子树。那么我们将 \(x\) rev了之后 \(x\)没有左子树了。也就是说,\(x\) 从该平衡树下最深的点变成最浅的点了,即该连通块的根。

剩下的操作都真·很简单了,说明都在注释代码里了。

 int find(int x)//找到 x 所在的 平衡树/联通块的根 (动态的树,有可能是一个森林,所有节点的根并不统一){access(x);splay(x);while(ls)pushdown(x),x=ls;splay(x);return x;}void splite(int x,int y)//我也不知道这个函数为什么叫 splite 明明就是在把 x->y 这条路径找出来好吧{make_root(x);access(y);splay(y);}void link(int x,int y)// 意义很明确的函数名称{make_root(x);if(find(y)!=x)t[x].ff=y;return ;}void cut(int x,int y)// 意义很明确的函数名称{make_root(x);if(find(y)==x&&t[y].ff==x&&t[y].ch[0]==0){t[y].ff=t[x].ch[1]=0;pushup(x);}return ;}

这里再说一个细节:cut 的判断:

if(find(y)==x&&t[y].ff==x&&t[y].ch[0]==0)

第一个部分判的是 \(x,y\) 是否同属一个连通块,第二部分判的是 \(y\) 的父亲是否为 \(x\).

而第三个判断就很抽象了,我们思考他的意义是什么:

首先,\(x\) 只有右子树。所以 \(dep[x]<dep[y]\). 那么如果 \(y\) 有左子树:\(dep[x]<dep[y_{ls}]<dep[y]\) 对应原树的路径就是 x <- \(y_{ls}\) <- y 。这种情况下边 (x,y) 是不存在的。

再补充一下 splay 的部分:由于我们之前 make_root 时有一个翻转左右儿子的操作,这个操作显然是要 从上到下 进行 pushdown 的,所以我们在 splay 时要先 从上到下 进行 pushdown

st[++st[0]]=y;while(isroot(y))st[++st[0]]=y=t[y].ff;//记录 x->root 上的每个点然后将它们从上到下 pushdownwhile(st[0])pushdown(st[st[0]--]);//应为树有可能发生左右翻转(换根时),所以需要 pushdown 来维护其现在的真实形态

然后这题就做完了。

Code:

#include<bits/stdc++.h>
const int N=1e5+5;
using namespace std;
int st[N];
struct LCT{struct Tree{int sum,val,tag,ff,ch[2];//首先明确,这颗平衡树的键值是该节点在原树中的深度}t[N<<2];#define ls t[x].ch[0]#define rs t[x].ch[1]#define fa t[x].ffinline bool isroot(int x){return (t[fa].ch[0]==x||t[fa].ch[1]==x);//这里的 isroot 返回的其实是他是否是 root 的反,所以其实是 isn'troot}inline void pushup(int x){t[x].sum=t[ls].sum^t[rs].sum^t[x].val;return;}inline void rev(int x){swap(t[x].ch[0],t[x].ch[1]);t[x].tag^=1;return ;}inline void pushdown(int x){if(t[x].tag){if(ls)rev(ls);if(rs)rev(rs);t[x].tag=0;}return ;}inline void rotate(int x){int y=fa,z=t[fa].ff,k=t[fa].ch[1]==x ? 1 : 0;if(isroot(y))t[z].ch[t[z].ch[1]==y]=x;//isn't roott[x].ff=z;t[y].ch[k]=t[x].ch[!k];if(t[x].ch[!k])t[t[x].ch[!k]].ff=y;t[x].ch[!k]=y;t[y].ff=x;pushup(y);}inline void splay(int x){int y=x,z=0;st[++st[0]]=y;while(isroot(y))st[++st[0]]=y=t[y].ff;//记录 x->root 上的每个点然后将它们从上到下 pushdownwhile(st[0])pushdown(st[st[0]--]);//应为树有可能发生左右翻转(换根时),所以需要 pushdown 来维护其现在的真实形态while(isroot(x))//isn't root{y=fa,z=t[fa].ff;if(isroot(y)){rotate((t[y].ch[1]==x)==(t[z].ch[1]==y) ? y : x);}rotate(x);}pushup(x);}void access(int x){int y=0;while(x){//每次 splay 完,x 是没有左子树的,因为在目前合并出来的这颗 平衡树上,x 在原树中的深度最小。//而它的右儿子又被我们强行赋为了 rt->x 这条链所形成的平衡树//这样就保证了在 access 完了之后,这颗平衡树维护只存在 x->root 这段路径上的点splay(x);rs=y;pushup(x);y=x;x=fa;}}void make_root(int x)//换根{access(x);splay(x);rev(x);}int find(int x)//找到 x 所在的 平衡树/联通块的根 (动态的树,有可能是一个森林,所有节点的根并不统一){access(x);splay(x);while(ls)pushdown(x),x=ls;splay(x);return x;}void splite(int x,int y)//我也不知道这个函数为什么叫 splite 明明就是在把 x->y 这条路径找出来好吧{make_root(x);access(y);splay(y);}void link(int x,int y)// 意义很明确的函数名称{make_root(x);if(find(y)!=x)t[x].ff=y;return ;}void cut(int x,int y)// 意义很明确的函数名称{make_root(x);if(find(y)==x&&t[y].ff==x&&t[y].ch[0]==0){t[y].ff=t[x].ch[1]=0;pushup(x);}return ;}
}T;
int n,m;
void work()
{cin>>n>>m;for(int i=1;i<=n;i++){scanf("%d",&T.t[i].val);}for(int i=1,opt,x,y;i<=m;i++){scanf("%d%d%d",&opt,&x,&y);if(opt==0){T.splite(x,y);printf("%d\n",T.t[y].sum);}if(opt==1){T.link(x,y);}if(opt==2){T.cut(x,y);}if(opt==3){T.splay(x);T.t[x].val=y;}}
}
int main()
{//freopen("LCT.in","r",stdin);freopen("LCT.out","w",stdout);work();return 0;
}

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

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

相关文章

深入探讨触发器的创建与应用:数据库自动化管理的强大工具

title: 深入探讨触发器的创建与应用:数据库自动化管理的强大工具 date: 2025/1/24 updated: 2025/1/24 author: cmdragon excerpt: 触发器是一种强大的数据库对象,它能够在特定事件发生之前或之后自动执行一组SQL语句。作为一种自动化管理工具,触发器在许多数据库管理场景…

Metasploit Pro 4.22.7-2025012201 (Linux, Windows) - 专业渗透测试框架

Metasploit Pro 4.22.7-2025012201 (Linux, Windows) - 专业渗透测试框架Metasploit Pro 4.22.7-2025012201 (Linux, Windows) - 专业渗透测试框架 Rapid7 Penetration testing, released Jan 22, 2025 请访问原文链接:https://sysin.org/blog/metasploit-pro-4/ 查看最新版。…

VS.net中快捷键收缩和展开代码段

i. Ctrl-M-O 折叠所有方法 ii. Ctrl-M-P 展开所有方法并停止大纲显示(不可以再折叠了) iii. Ctrl-M-M 折叠或展开当前方法 iv. Ctrl-M-L展开所有方法 其他的快捷方式:怎样跳转到指定的某一行? 两种方法:Ⅰ. Ctrl+G Ⅱ. 双击状态栏中的行号2.. 怎样创建矩形选区? 两…

查看Resources.resx的三种方式

同一个Resources.resx文件在Visual Studio 中可以以多种方式查看, 但某一天遇到了问题, 只能以资源浏览器的方式查看, 期初还以为是Visual Studio升级加入的新功能, 怎么都无法打开设计器, 在Resources.resx文件右键菜单中看不到下面的菜单项,经过一些列的摸索, 发现设置…

Vmware 虚拟机克隆注意事项-CentOS7

1.克隆后修改MAC地 2.修改 UUID,UUID克隆虚拟机后一般是一样的,需要进行修改 输入命令uuidgen,将生成的UUID写入ifcfg-ens33 【文件位置:/etc/sysconfig/network-scripts/ifcfg-ens33】 【我克隆的时候并没有修改,但是也一样可以联网,不知道为啥】 3.修改主机名 先临时修…

autocad Ribbon创建的一种新思路

之前开发的功能相对简单, 一个RibbonTab就把相关的功能展示出来了。 目前着手准备开发的功能, 需要多个RibbonTab, 且不想显示AutoCAD或Civil 3D自身的RibbonTab, 曾经想模拟3d3s的样子来切换RibbonTab, 但发现3d3s是将AutoCAD原生的Cuix和自己的RibbonTab结合到一起, 对…

Solon Cloud Gateway 开发:导引

Solon Cloud Gateway 是 Solon Cloud 体系提供的分布式网关实现(轻量级实现)。Solon Cloud Gateway 是 Solon Cloud 体系提供的分布式网关实现(轻量级实现)。 分布式网关的特点(相对于本地网关):提供服务路由能力 提供各种拦截支持1、分布式网关推荐 建议使用专业的分布…

Mac安装Prometheus + Grafana

一、安装Prometheus 1、下载安装 brew install prometheus2、安装路径 /opt/homebrew/Cellar/prometheus/3.1.0 3、修改配置文件 默认配置文件路径:/opt/homebrew/etc/prometheus.yml global:scrape_interval: 15sscrape_configs:- job_name: "prometheus"static_co…

Python基础6——装饰器(续) 递归 模块

1.函数1.1 参数当默认参数的值为可变类型时慎用# 不推荐使用以下代码 def func(data, value=[]):pass可以将默认参数的值改为None# 推荐使用以下代码 def func(data, value=None):if not value:value = []案例def func(data, value=[]):value.append(data)return valuev1 = fun…

C# Winform 在 Pancel 上绘制矩形

在C#的WinForms应用程序中,Panel控件本身不直接支持绘图功能,因为它不是一个绘图控件。不过,你可以通过在Panel上覆盖(override)OnPaint方法或者使用Graphics对象来在Panel上绘制图形。下面是如何实现这两种方法的示例: 方法1:覆盖OnPaint方法 可以通过重写Panel的OnPai…

SpringBoot使用SSE流,打tar包发版后出现问题

SpringBoot使用SSE流,打tar包发版后出现问题 以下纯个人实践,如有问题,还望指正~ 出现的问题 出现原因:本地调试SSE推送数据没有问题,但是通过打包为tar包发版之后,出现了以下报错: 主要问题就是: java.lang.IllegalArgumentException: Async support must be enabled …

【每日一题】20250124

读书就是这样好,无论心不在焉,板着长脸,只要考试及格,就是一个及格的人。【每日一题】 1.(20分) \(\hspace{0.6cm}\)如图所示,质量 \(M=2 \; \mathrm{kg}\) 的滑块套在光滑的水平轨道上,质量 \(m=1 \; \mathrm{kg}\) 的小球通过长 \(L=0.5 \; \mathrm{m}\) 的轻质细杆与…