文章目录

  • 前言
  • 解析
    • 原理
    • rotate(x)
    • splay(x)
    • access(x)
    • findroot(x)
    • makeroot(x)
    • split(x,y)
    • link(x,y)
    • cut(x,y)
    • pushdown(x)
  • 完整代码

所谓Link Cut Tree,就是林可卡特发明的tree

(逃)

前言

终于走到了这一天…
其实感觉没有预想的那么难(单纯就学习算法和理解模板而言)
至少感觉比学平衡树轻松

LCT是一种很强大的树形数据结构
最重要的功能优势就是支持单log的复杂度完成加边、删边、换根的操作
在路径问题上十分强大

解析

原理

LCT的核心原理是实链剖分
和重链剖分类似的,将一棵树中的边分为实边和虚边
每个结点的最多有一条连向儿子的实边(与树链剖分不同的,也可以没有)

把实边连成的链成为实链
把在同一条实链上的结点放到同一个splay中
这个splay的中序遍历就是这条链从上到下的顺序
这样就间接的表示了所有实边的信息

那么如何表示虚边?
假设原树上有一条 fafafa 指向sonsonson的虚边
就把son所在的splay的根节点指向fa,从而表示出虚边
根节点也有父亲了,如何判断根节点呢?
只有该结点的父亲的左右儿子都不是自己,那么这个结点就是根节点

inline bool isroot(int x) {return tr[f[x]][0]!=x&&tr[f[x]][1]!=x;
}

下面我们来讲讲具体的操作函数

rotate(x)

和普通的splay大同小异,唯一需要注意的是有一句

if(gfa) tr[gfa][which(fa)]=x;

变成了:

if(!isroot(fa)) tr[gfa][which(fa)]=x;

较好理解,因为判定根的条件变了

splay(x)

将x转到所在splay的根节点,由于只需要转到根一个功能,穿一个参即可
注意:不同于正常的splay,需要先把链上的标记下放
实现有两种方法
第一种是递归:

void pushall(int x){//printf("%d\n",x);if(!isroot(x)) pushall(f[x]);pushdown(x);
}

第二种是手写栈:

int y=x,top=0;zhan[++top]=y;while(!isroot(y)) zhan[++top]=y=f[y];

建议使用第二种,因为第一种不小心容易写完函数后忘记调用(别问我为什么知道)
然后和rotate类似的,对根的判断略有区别
不过都是一个道理

inline void splay(int x) {int y=x,top=0;zhan[++top]=y;while(!isroot(y)) zhan[++top]=y=f[y];while(top) pushdown(zhan[top--]);for(int fa; fa=f[x],!isroot(x); rotate(x)) {if(!isroot(fa)) which(fa)==which(x)?rotate(fa):rotate(x);}return;
}

access(x)

重中之重,LCT的灵魂

功能:提取出 x 到根节点的一条实链

先把x的尾巴去掉,具体来说就是转到根,然后砍掉右子树
然后一路往上直至 fa 指针为空,过程中需要改变实虚边的状态

inline void access(int x) {for(int y(0); x; y=x,x=f[x]) {splay(x);tr[x][1]=y;pushup(x);if(y) f[y]=x;}return;
}

findroot(x)

功能:找到x所在的树的根节点(注意是真实的树而不是splay的根)

先access(x),然后把x转到根,不断往左走即可

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

makeroot(x)

功能:把x作为其所在树的根(换根)
前面说过,父子关系在splay中的体现是中序遍历
所以换根(即颠倒x-rt路径上所有的父子关系),其实就是区间翻转
先access(x),然后转到根,然后用类似文艺平衡树的方式在x上打一个翻转标记即可

inline void makeroot(int x) {access(x);splay(x);tag(x);return;
}

(最愉快的代码)

split(x,y)

功能:提取出以x、y为两端点的实链(前提是二者联通)

makeroot(x)之后access(y)即可
为了后面方便,常常再加一步splay(y)(不然连根是谁都不知道提取个寂寞)

inline void split(int x,int y){//y is fathermakeroot(x);access(y);splay(y);return;
}

link(x,y)

功能:加一条边(x,y)(x,y)(x,y)(在有出现环的时候忽略操作)

出现环(即xy原来就联通)可以用findroot判掉
否则,先makeroot(x),然后把f(x)指向y即可
记得按需要更新y的信息!

inline void link(int x,int y) {makeroot(x);if(findroot(y)==x) return;f[x]=y;pushup(y);//printf("link: %d -> %d\n",x,y);
}

cut(x,y)

功能:切断边(x,y)(x,y)(x,y)(在没有这条边时忽略操作)

先makeroot(x),然后access(y),splay(y)到根
此时如果存在这条边,必然x和y是在同一个splay中
判断的充要条件:x是y的左儿子且x没有右儿子,比较显然
如果存在的话,把这条y的左儿子和x的父亲赋值为0即可
不要忘记更新y的信息(又一次)

inline void cut(int x,int y) {makeroot(x);access(y);splay(y);if(tr[y][0]!=x||tr[x][1]) return;tr[y][0]=f[x]=0;pushup(y);return;
}

pushdown(x)

大部分时候只需要翻转
一个看到的地方都说比较“稳妥”的写法
(谁不喜欢稳妥呢)

inline void tag(int x) {if(x) {rev[x]^=1;swap(tr[x][0],tr[x][1]);}
}
inline void pushdown(int x) {if(rev[x]){rev[x]=0;tag(tr[x][0]);tag(tr[x][1]);}return;
}

完整代码

经验传送门

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e5+100;
const int mod=1e9+7;
const double eps=1e-9;
inline ll read() {ll x(0),f(1);char c=getchar();while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}int n,m;#define ls(o) tr[o][0]
#define rs(o) tr[o][1]
int tr[N][2],f[N],rev[N],val[N],mx[N];
inline bool isroot(int x) {return tr[f[x]][0]!=x&&tr[f[x]][1]!=x;
}
inline bool which(int x) {return tr[f[x]][1]==x;
}
inline void pushup(int x) {if(x){mx[x]=x;if(ls(x)&&val[mx[ls(x)]]>val[mx[x]]) mx[x]=mx[ls(x)];if(rs(x)&&val[mx[rs(x)]]>val[mx[x]]) mx[x]=mx[rs(x)];}return;
}
inline void tag(int x) {if(x) {rev[x]^=1;swap(tr[x][0],tr[x][1]);}
}
inline void pushdown(int x) {if(rev[x]){rev[x]=0;tag(tr[x][0]);tag(tr[x][1]);}return;
}
void debug(int x) {if(!x) return;pushdown(x);debug(tr[x][0]);printf("debug: x=%d ls=%d rs=%d\n",x,tr[x][0],tr[x][1]);debug(tr[x][1]);return;
}
inline void rotate(int x) {int fa=f[x],gfa=f[fa];int d=which(x),son=tr[x][d^1];f[x]=gfa;if(!isroot(fa)) tr[gfa][which(fa)]=x;f[fa]=x;tr[x][d^1]=fa;if(son){f[son]=fa;}tr[fa][d]=son;pushup(fa);pushup(x);return;
}
int zhan[N];
inline void splay(int x) {int y=x,top=0;zhan[++top]=y;while(!isroot(y)) zhan[++top]=y=f[y];while(top) pushdown(zhan[top--]);for(int fa; fa=f[x],!isroot(x); rotate(x)) {if(!isroot(fa)) which(fa)==which(x)?rotate(fa):rotate(x);}return;
}
inline void access(int x) {for(int y(0); x; y=x,x=f[x]) {splay(x);tr[x][1]=y;pushup(x);if(y) f[y]=x;}return;
}
inline void makeroot(int x) {access(x);splay(x);tag(x);return;
}
inline int findroot(int x) {access(x);splay(x);while(pushdown(x),tr[x][0]) x=tr[x][0];splay(x);return x;
}
inline void link(int x,int y) {makeroot(x);if(findroot(y)==x) return;f[x]=y;pushup(y);//printf("link: %d -> %d\n",x,y);
}
inline void cut(int x,int y) {makeroot(x);access(y);splay(y);if(tr[y][0]!=x||tr[x][1]) return;tr[y][0]=f[x]=0;pushup(y);return;
}
inline void split(int x,int y){//y is fathermakeroot(x);access(y);splay(y);return;
}

模板:Link Cut Tree(LCT)相关推荐

  1. BZOJ 3282 Link Cut Tree (LCT)

    题目大意:维护一个森林,支持边的断,连,修改某个点的权值,求树链所有点点权的异或和 洛谷P3690传送门 搞了一个下午终于明白了LCT的原理 1 #include <cstdio> 2 # ...

  2. Link Cut Tree 学习笔记

    Link Cut Tree 学习笔记 说在前边 最近补 CF 碰见一道 LCT ,就打算学习一下这个东西...顺便复习一下 splay. 具体算法及实现 参考了FlashHu, Candy? P369 ...

  3. Link/Cut Tree学习笔记

    最近正是实验课的高峰期,我数了一下,除了毛概没有实验课,其他的课都有实验课...不过好在这些实验都不是很难.我尽力挤出时间用来刷题. 简介 Link/Cut Tree和树链剖分很相似,二者处理的问题也 ...

  4. Link Cut Tree详解

    Link Cut Tree ==Warning:千万不要跳读== 参考博客:https://www.cnblogs.com/flashhu/p/8324551.html 什么是动态树? 动态树问题, ...

  5. 15行代码AC——Link/Cut Tree CodeForces - 614A(爆long long处理+快速幂讲解)

    励志用少的代码做高效表达 Problem describe Programmer Rostislav got seriously interested in the Link/Cut Tree dat ...

  6. link cut tree 入门

    鉴于最近写bzoj还有51nod都出现写不动的现象,决定学习一波厉害的算法/数据结构. link cut tree:研究popoqqq那个神ppt. bzoj1036:维护access操作就可以了. ...

  7. luoguP3690 【模板】Link Cut Tree (动态树)[LCT]

    题目背景 动态树 题目描述 给定N个点以及每个点的权值,要你处理接下来的M个操作.操作有4种.操作从0到3编号.点从1到N编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor ...

  8. 洛谷 - P3690 【模板】Link Cut Tree (动态树)(LCT模板)

    题目链接:点击查看 题目大意:给出 n 个带权节点,需要执行 m 次操作,每次操作分为四种类型: 0 x y 代表询问从 x 到 y 的路径上的点的权值的 xor 和.保证 x 到 y 是联通的. 1 ...

  9. luogu P3690 【模板】Link Cut Tree (动态树)

    嘟嘟嘟 LCT竟然看了整整一天,但好歹是看懂了. 教程这里不写,强烈推荐 闪狐大佬的博客 . 但是还是有几句想说的. 1.尽管LCT和splay很像,但是有一些细节还是不一样的.首先是rotate,我 ...

最新文章

  1. nagios报警不发邮件
  2. JavaScript DOM编程艺术 - 读书笔记1-3章
  3. php 单例类 mysql pdo_PHP实战:PHP基于单例模式编写PDO类的方法
  4. [以太坊源代码分析] V. 从钱包到客户端
  5. 问题:linux系统经常出现断网的情况,重启之后系统恢复正常
  6. 主机不支持php5.4,GoDaddy Linux主机不再支持PHP5.3版本 | Godaddy美国主机中文指南
  7. shell脚本:批量修改文件名(文件名中添加字符)
  8. python设计模式之MVC
  9. matlab 一阶惯性环节,一阶惯性环节
  10. 计算机一级excel典型试题,最新excel计算机一级试题合集
  11. Keytool命令详解
  12. 朱令被投毒案关键人物语料分析之孙维篇
  13. Ant Counting
  14. c语言“递归系列”:递归实现x的n次方
  15. php __destruct反序列化原理
  16. 依概率收敛和依分布收敛(附一道例题)
  17. 简单的YouTube菜单效果
  18. 基于java+springboot+mybatis+vue+elementui的B2C购物电商平台设计与实现
  19. Makefile造成“ make: *** No rule to make target ' xxx ', needed by 'xxx'。 停止。”错误的一种原因
  20. epoll原理与实现

热门文章

  1. android什么是回调,Android中的回调是什么?
  2. mysql binlog 大数据_后起之秀 | MySQL Binlog增量同步工具go-mysql-transfer实现详解
  3. Java面试会问的——数组、声明、初始化、冒泡、多维数组、稀疏数组
  4. 计算机 运行命令,教你电脑运行命令
  5. java 线程访问控件_C#多线程与跨线程访问界面控件的方法
  6. 凝思系统改时间_国产操作系统往事:四十年激变,终再起风云
  7. 《笨办法学python》6_笨办法学Python 习题 25: 更多更多的练习
  8. ajax jquery php_基于Ajax jquery的使用php登录
  9. MBR的Linux分区机制启动过程,linux系统启动流程(MBR)
  10. js变量和java变量相等,js中变量和jsp中java代码中变量互相访问解决方案