最近在复习学过的省选算法,发现以前学的太不扎实了,太逊了,有必要做笔记整理一下

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 笔记的更多相关文章

  1. PJ可能会用到的动态规划选讲-学习笔记

    PJ可能会用到的动态规划选讲-学习笔记 by Pleiades_Antares 难度和速度全部都是按照普及组来定的咯 数位状压啥就先不讲了 这里主要提到的都是比较简单的DP 一道思维数学巧题(补昨天) ...

  2. JavaScript、全选反选-课堂笔记

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. 算法复习——LCT(bzoj2049洞穴勘测)

    题目: Description 辉辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连 ...

  4. LCT笔记

    先存个代码 #include<iostream> #include<cstring> #include<cstdio> #include<cmath> ...

  5. FHQ treap学习(复习)笔记

    .....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...

  6. 应聘复习基础笔记1:网络编程之TCP与UDP的优缺点,TCP三次握手、四次挥手、传输窗口控制、存在问题

    重要性:必考 一.TCP与UDP的优缺点 ①TCP---传输控制协议,提供的是面向连接.可靠的字节流服务.当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据.TCP提供 ...

  7. tarjan学习(复习)笔记(持续更新)(各类找环模板)

    题目背景 缩点+DP 题目描述 给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个点,但是,重复经过的点,权值只 ...

  8. LCT做题笔记

    最近几天打算认真复习LCT,毕竟以前只会板子.正好也可以学点新的用法,这里就用来写做题笔记吧.这个分类比较混乱,主要看感觉,不一定对: 维护森林的LCT 就是最普通,最一般那种的LCT啦.这类题目往往 ...

  9. C#复习笔记(5)--C#5:简化的异步编程(异步编程的深入分析)

    首先,阐明一下标题的这个“深入分析”起得很惭愧,但是又不知道该起什么名字,这个系列也主要是做一些复习的笔记,供自己以后查阅,如果能够帮助到别人,那自然是再好不过了. 然后,我想说的是异步方法的状态机真 ...

随机推荐

  1. jdbc编程学习之增删改查(2)

    一,enum类型的使用 在SQL中没有布尔类型的数据,我们都使用过布尔类型,当属性的值只用两种情况时.例如性别等.那在数据库对这些属性的值个数比较少时我们应该使用什么数据类型呢?SQL给我们提供了枚举 ...

  2. [leetcode712]204. Count Primes寻找范围内的素数

    厄拉多塞筛选法,就是哈希表记录素数的倍数 public int countPrimes(int n) { /* 牛逼哄哄的厄拉多塞筛选法 就是从2开始,每找到一个素数,就把n以内的这个数的倍数排除 记 ...

  3. Qt学习笔记-嵌入式qt程序支持显示中文

    移植后得qt程序在开发板上运行时无法显示中文. 拷贝windows中的字体也不行. 从网上找到方法. 添加以下代码:需要头文件  #include <QTextCodec> QTextCo ...

  4. JS验证三种提示框

    1 <form> 2 <!-- 按钮选择点击 --> 3 <tr> 4 <td><input type="button" na ...

  5. 多人VNC远程桌面服务配置

    博主前篇博客里面已经讲过VNC的配置,为了方便查看,单独拿出来写一下. (1) 搜索可以用来安装vncserver的软件包: sudo apt-cache search vncserver (2) 安 ...

  6. JavaScript--总结一(变量+数据类型+运算符)

    JavaScript是什么? 是一门脚本语言(不需要编译,直接执行) 是一门解释性语言 是一门动态类型的语言 是一门基于对象的语言 JavaScript分为三个部分 1.ECMAScript  标准- ...

  7. springboot 日期参数前后台转换问题

    方式 一: 在实体类上加@DatetimeFormat与@JsonFormat注解 @DatetimeFormat:将前台日期字符串转换成Date格式 @DateTimeFormat(pattern= ...

  8. 设计模式之SOLID原则

    介绍 设计模式中的SOLID原则,分别是单一原则.开闭原则.里氏替换原则.接口隔离原则.依赖倒置原则.前辈们总结出来的,遵循五大原则可以使程序解决紧耦合,更加健壮. SRP 单一责任原则 OCP 开放 ...

  9. Javascript函数闭包及案例详解

    什么情况下会形成闭包,什么是闭包 闭包(Closure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包 可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员 下面来看一个 ...

  10. 域名解析 看Cname 信息

    CMD 命令: nslookup -q=cname www.yuzhentan.com