省选复习 - LCT 笔记
最近在复习学过的省选算法,发现以前学的太不扎实了,太逊了,有必要做笔记整理一下
LCT 笔记
主要功能
在线维护树的加边和删边,以及树相关信息
复杂度只有一个 \(\log\),乘以维护信息的复杂度(一般是 \(O(1)\))
和其它数据结构的比较
逊的:
- 并查集+线段树分治:只能离线,而且不好维护树上的信息,倾向于维护联通块
- 轻重链剖分:缺少加边和删边的功能,而且多一个 \(\log\) 的复杂度
- 动态插入倍增:逊,只能加入一个点
- 树分块:难以实现加边和删边(也可能是我不会),而且复杂度是根号的
老大哥:
- top tree ,yyds (我不会qaq
思想
考虑对轻重链剖分进行魔改
轻重剖分是怎么剖的?一开始预处理出 \(size\),钦点 \(size\) 最大的是重儿子。
那么如果要支持加边和删边,每次重构显然不现实。
于是想到,也许不一定非要找最大的,方便修改就行了。
虚实剖分
这是轻重剖分的动态升级版,资瓷动态的钦点,哪些边是“实”的,哪些边是“虚”的。每个点只能有一个实儿子。然后实边连起来变成实链,将树划分成若干条链。
LCT全部都是基于虚实剖分,动态钦点重儿子,来实现链操作的。
如何维护所有的链
实链
考虑一下加边和断边的时候,如果把实链看成区间,其实就是在做区间合并和分裂操作,以及对一块区间做操作或者询问(链修改/询问)。那么什么结构可以快速的维护这东西呢?
Splay
于是维护方式为:用若干颗 \(Splay\) 维护若干个实链,每一颗 \(Splay\) 树的中序遍历,就对应一条实链从上往下的遍历。
举个栗子
这张图中,红色是实边,蓝色是虚边。
图中,\(A,B,D,H\) 在一个 Splay 里,\(E,I\) 在一个 Splay 里,\(C,G\) 在一个 Splay 里,\(F,J\) 两点都是单独一个点作为一个 Splay 的。
虚边
现在实边维护出来了,还有一个问题:虚边我也要记下来啊
既然要记下来,怎么和原来的实边区分开来?
有一个巧妙的实现,认父不认子 —— 我可能是你父亲,但你不一定是我儿子。
对于点 \(u\),如果 \(u\) 既不是 \(fa_u\) 的左儿子,也不是右儿子,那么 \((u,fa_u)\) 这条边就是虚边了。
开始构思
我们站在 Tar 老师的角度,考虑这个算法怎么实现。
首先是功能:支持加边,删边,链修改/询问。具体怎么做呢?
你可能会想,加边不是直接合并一下两个 Splay 就完了。删边也是,直接断一下即可。
但这会破坏现在剖好的链,而且无法维护。
考虑把根换成 \(u\),这样倒是可以直接操作。那我们就要支持一个换根操作了。
那么如何换根呢?
考虑把树想象成有向的,每条边从父亲连向儿子。那只要把 \(u\) 到根的路径边都反向一下,那 \(u\) 就是根了。由于我们用的是 Splay 维护,反向还是挺好搞的,就交换一下左右儿子即可。这个可以用 lazytag 实现。
那现在又要提取到根的路径了。这便是 LCT 的基础操作: access
具体要维护的功能(从基础到高级)
Splay部分
需要支持把每个点旋转到它 所在Splay 的根。根定义为:如果这个点和父亲连的是虚边,那它就是它所在 Splay 的根。
一开始 Splay 的根是它维护的实链最上面那个点,然而后期我们会对树做旋转,就导致 Splay 的根可能发生变化。
access(u)
这是最基本的:提取 \(u\) 到根的路径(把路径上都变成实边,并把相应的边变成虚边)。另外,它会把 \(u\) 下面的那个实边给变虚,这就使得 \(u\) 所在的 Splay 恰好变成 \(u\) 到根的路径,不多任何别的。
想起来很简单,每次把 \(u\) 给 \(Splay\) 到最顶上,然后在右儿子连接上一个跨过去的 Splay。特殊地,跨过的第一个 Splay 连空点 —— 这就把 \(u\) 下面那个实边变虚了。
为什么连右儿子?考虑现在的 Splay 维护的链是 \(a\),上一个跨过去的链是 \(a'\)
那么我们既然要把边变成实的,就要把 \(a\) 和 \(a'\) 连起来,其中 \(a'\) 在 \(a\) 的后面,因为这次的链在上次的链上面。
所以要接在 后面,体现在 Splay 的中序遍历,就是接在 右儿子
此时顺便 update 一下信息。
make(u)
把 \(u\) 变成当前树的根。
由上面所说,先 access 一下,Splay 上去。此时由 Splay 的中序遍历,我们是从最下面翻上来的,所以 \(u\) 应该在 Splay 的根,并且其他点都在它的左边(即 \(u\) 的右儿子为空)
然后打一个 lazytag 维护边的反转即可。
find(u)
老样子,先 access 一下,然后 Splay 上去
上面说了,现在所有点都在 \(u\) 左边,并且原来的那个根应该在最左边 —— 它是原树中链的顶头,在 Splay 上便是最左边的了。
然后一直走左儿子,就可以找到根了。最后找到根完了再 Splay 上去,保证复杂度
split(u,v)
打通 \(u,v\) 之间的路径,并且不剩别的
先把 \(u\) 变成根(make(u)),然后把 v 给 access 上去即可。Splay 一下保证复杂度。
link(u,v)
终于到了正题
上面说不好直接连,现在我们会换根了,make一下,然后连就可以了
注意,题目如果不保证连边合法,先 find 一下看看在不在一块
cut(u,v)
正题*2
先make(u)一下,然后来一个find(v)
find的时候要做一次access,最后把找到的根又 Splay 上去了。那么现在一定满足:
- \(u\) 是根节点
- \(u\) 只有一个儿子 \(v\),并且是右儿子(由于 \(u\) 在 \(v\) 上面)。
- \(u,v\) 的路径中间没有其他的点,体现在 Splay 上,就是 \(v\) 没有左儿子
然后把 \(u\) 的右儿子和 \(v\) 的父亲都设置为空,即可
复杂度
log的,不知道咋分析
网上的势能分析看不懂qaq
为何我的眼里常含泪水,因为我菜的抠脚 —— LightningUZ
talk is cheap
#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
#define N 1000006
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
#define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
#define p_b push_back
#define sz(a) ((int)a.size())
#define all(a) a.begin(),a.end()
#define iter(a,p) (a.begin()+p)
int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
template <typename T> void Rd(T& arg){arg=I();}
template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
class Link_Cut_Tree
{
public:
int ch[N][2],fa[N]; // 基本, 维护splay的
bool rv[N]; // makeroot要用的翻转标记
int val[N],s[N]; // 其它要维护的 (这里是路径异或)
#define ls(u) ch[u][0]
#define rs(u) ch[u][1]
bool id(int u) {return u==rs(fa[u]);}
bool rt(int u) {return u!=ls(fa[u]) and u!=rs(fa[u]);}
// 判断是否是 splay 的根
void up(int u) // 维护信息
{
s[u]=s[ls(u)]^s[rs(u)]^val[u];
}
void revone(int u)
{
swap(ls(u),rs(u));
rv[u]^=1;
}
void pushdown(int u) // pushdown
{
if (rv[u])
{
if (ls(u)) revone(ls(u));
if (rs(u)) revone(rs(u));
rv[u]=0;
}
}
void pushall(int u) // 一连串的pushdown
{
if (!rt(u)) pushall(fa[u]);
pushdown(u);
}
void rot(int u) // 最基本的旋转
{
int f=fa[u],f2=fa[f],i=id(u),i2=id(f),w=ch[u][i^1];
if (!rt(f)) ch[f2][i2]=u; ch[u][i^1]=f; ch[f][i]=w;
if (w) fa[w]=f; fa[f]=u; fa[u]=f2;
// 这两行顺序不能反
// 见洛谷讨论区
up(f),up(u);
}
void splay(int u)
{
pushall(u);
while(!rt(u)) // 这里是 while(!rt(u)),而不是 while(u)
{
int f=fa[u];
if (!rt(f))
{
rot(id(f)==id(u)?f:u);
}
rot(u);
}
up(u);
}
void access(int u) // access
{
for(int p=0;u;u=fa[p=u]) // p: 上一块跨过的splay的根
{
splay(u); rs(u)=p; up(u);
}
}
void make(int u)
{
access(u); splay(u); revone(u);
}
int find(int u)
{
access(u); splay(u);
while(ls(u)) pushdown(u),u=ls(u); // 这里不要忘了先pushdown
splay(u); return u; // 也不要忘了splay
}
void split(int u,int v) // 提取路径
{
make(u); access(v); splay(v);
}
void link(int u,int v)
{
make(u);
if (find(v)!=u) // 判一下find
{
fa[u]=v;
}
}
void cut(int u,int v)
{
make(u);
if (find(v)==u and rs(u)==v and !ls(v)) // 判一下find*2
{
rs(u)=fa[v]=0;
}
}
}T;
int n,m;
void Input()
{
Rd(n,m);
F(i,1,n) T.val[i]=I();
}
void Sakuya()
{
F(i,1,m)
{
int o,x,y; Rd(o,x,y);
if (o==0)
{
T.split(x,y);
printf("%d\n",T.s[y]);
}
if (o==1)
{
T.link(x,y);
}
if (o==2)
{
T.cut(x,y);
}
if (o==3)
{
T.splay(x);
T.val[x]=y;
}
}
}
void IsMyWife()
{
Input();
Sakuya();
}
}
#undef int //long long
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();
return 0;
}
省选复习 - LCT 笔记的更多相关文章
- PJ可能会用到的动态规划选讲-学习笔记
PJ可能会用到的动态规划选讲-学习笔记 by Pleiades_Antares 难度和速度全部都是按照普及组来定的咯 数位状压啥就先不讲了 这里主要提到的都是比较简单的DP 一道思维数学巧题(补昨天) ...
- JavaScript、全选反选-课堂笔记
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 算法复习——LCT(bzoj2049洞穴勘测)
题目: Description 辉辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连 ...
- LCT笔记
先存个代码 #include<iostream> #include<cstring> #include<cstdio> #include<cmath> ...
- FHQ treap学习(复习)笔记
.....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...
- 应聘复习基础笔记1:网络编程之TCP与UDP的优缺点,TCP三次握手、四次挥手、传输窗口控制、存在问题
重要性:必考 一.TCP与UDP的优缺点 ①TCP---传输控制协议,提供的是面向连接.可靠的字节流服务.当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据.TCP提供 ...
- tarjan学习(复习)笔记(持续更新)(各类找环模板)
题目背景 缩点+DP 题目描述 给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个点,但是,重复经过的点,权值只 ...
- LCT做题笔记
最近几天打算认真复习LCT,毕竟以前只会板子.正好也可以学点新的用法,这里就用来写做题笔记吧.这个分类比较混乱,主要看感觉,不一定对: 维护森林的LCT 就是最普通,最一般那种的LCT啦.这类题目往往 ...
- C#复习笔记(5)--C#5:简化的异步编程(异步编程的深入分析)
首先,阐明一下标题的这个“深入分析”起得很惭愧,但是又不知道该起什么名字,这个系列也主要是做一些复习的笔记,供自己以后查阅,如果能够帮助到别人,那自然是再好不过了. 然后,我想说的是异步方法的状态机真 ...
随机推荐
- 使用@Param注解
1,使用@Param注解 当以下面的方式进行写SQL语句时: @Select("select column from table where userid = #{userid} " ...
- [leetcode]187. Repeated DNA Sequences寻找DNA中重复出现的子串
很重要的一道题 题型适合在面试的时候考 位操作和哈希表结合 public List<String> findRepeatedDnaSequences(String s) { /* 寻找出现 ...
- Qt学习笔记-Qtcreator的webkit和qt4.7.0的版本有关
之前下载了一个最新的是qtcreator,是通过ubuntu的是apt-get下载的.可是里面没有webkit控件.网上的网友说是最新的没有了.要用老版的,于是下载了一个2.5.2的就正常了. 用老版 ...
- 发起一个开源项目:基于 .NET 的博客引擎 fluss
今天我们发起一个开源项目,它的名字叫 fluss,fluss 是 river 的德语. 百川归海,每一个博客就如一条河流,输入的是文字,流出的是知识,汇入的是知识的汪洋大海. 川流不息,fluss 是 ...
- 单细胞分析实录(1): 认识Cell Hashing
这是一个新系列 差不多是一年以前,我定导后没多久,接手了读研后的第一个课题.合作方是医院,和我对接的是一名博一的医学生,最开始两边的老师很排斥常规的单细胞文章思路,即各大类细胞分群.注释.描述,所以起 ...
- Docker容器技术--自定义网桥后的默认网卡名称
新建docker虚拟网络命令 这里以172.18.0.1为例,名字为clusterdocker network create --subnet=172.18.0.0/16 cluster 当我们想新建 ...
- JAVA JVM助记符
ldc:将int/float/String类型的常量值从常量池中推送至栈顶(栈顶的值是即将要用的) bipush:将单字节(-128 ~ 127)的常量值从常量池中推至栈顶 sipush:将一个短整型 ...
- 深入理解Go Context
目录 emptyCtx类型 cancelCtx类型 timerCtx类型 valueCtx类型 在Go语言并发编程中,用一个goroutine来处理一个任务,而它又会创建多个goroutine来负责不 ...
- 【C++】《Effective C++》第七章
第七章 模板与泛型编程 条款41:了解隐式接口和编译期多态 面向对象设计中的类(class)考虑的是显式接口(explict interface)和运行时多态,而模板编程中的模板(template)考 ...
- 开源:AspNetCore 应用程序热更新升级工具(全网第一份公开的解决方案)
1:下载.开源.使用教程 下载地址:Github 下载 .其它下载 开源地址:https://github.com/cyq1162/AspNetCoreUpdater 使用教程: 解压AspNetCo ...