LCT 学习笔记

首先,\(LCT\) 一般指疏松结缔组织。 疏松结缔组织是指一种柔软而富有弹性的结缔组织。主要填充在各器官或组织间的间隙中,如剥制动物标本时,将皮肤拉开,就可见到薄而透明的疏松结缔组织。

对不起,刚才生物同志走错片场了。


\(LCT(Link\ Cut\ Tree)\),是一种可以解决动态树问题的数据结构。

动态树问题的身份证号通常有加边、删边和对某条路径进行修改或查询,当然,图永远都是森林。

实际上,假如没有加边和删边操作,动态树问题就是重链剖分。我们考虑沿用重链剖分的思路,定义一种新的树链剖分:

实链剖分:我们人为的定义实边和虚边,每个点与它儿子连的边中,实边数量 \(\le 1\)。其他部分和重链剖分相似。需要注意的是,实边可以变成虚边,虚边也可以变成实边,只要满足上述条件即可

重链剖分中,我们通常使用线段树进行修改和查询,那么 \(LCT\) 里,我们同样需要一种能够实现区间操作的数据结构。对,他就是 \(Splay\)(大崩,我上一次写 \(Splay\) 的时候,维护儿子还用的是 \(ls/rs\))。

那么我们该怎么用 \(Splay\) 维护实链呢?


这个时候我们就需要引进 辅助树 了。

辅助树中,每一棵 \(Splay\) 都对应原森林中的一条实链。实链和实链间用虚边相连,具体方法是将所有链头不是根的实链的链头,和这条链中在原树中深度最小的点在原树中的父亲相连。所以 \(LCT\) 认父不认子(好啊,又是一个乱伦家庭)。

可以发现,所有对链的操作都可以单纯靠辅助树来实现,所以我们只需要维护辅助树就好了。

那么对于一条询问的路径,我们该如何操作呢?既然我们都用了 \(Splay\),就要好好利用 \(splay\) 函数,所以我们 直接将这条路径定义成一条实链,就可以直接区间修改了!

由于认父不认子,所以从下往上一路 \(splay\) 过去就好。对应的操作就是 \(access\)。具体怎么做,可以去看 \(flashhu\) 大佬讲的,还有图,这里不多赘述。

可以证明均摊时间复杂度为 \(O(n\log n)\),是比重链剖分优的。

当然,\(LCT\) 中的 \(Splay\) 部分有一定变化,这点要注意。

下面给出几个重要函数的代码:

#define fa(x) lct[x].fa//父亲
#define fl(x) lct[x].fl//标记
#define sn(x,i) lct[x].sn[i]//splay中的儿子
//这里通常还会有几个需要push_up的东西
struct node{
    int sn[2],fa,fl;
}lct[N];int tp,st[N];
int check(int x){
    return sn(fa(x),0)!=x&&sn(fa(x),1)!=x;
}//是否是根
int chksn(int x){
    return sn(fa(x),1)==x;
}//是左儿子还是右儿子
void push_up(int x){
	//具体问题具体书写
}//合并儿子信息
void down(int x){
    swap(sn(x,0),sn(x,1)),fl(x)^=1;
}void push_down(int x){
    if(!x||!fl(x)) return;fl(x)=0;
    if(sn(x,0)) down(sn(x,0));
    if(sn(x,1)) down(sn(x,1));
}//标记下放
void rotate(int x){
    int y=fa(x),z=fa(y),k=chksn(x);
    if(!check(y))
        sn(z,chksn(y))=x;
    fa(x)=z,fa(y)=x,fa(sn(x,1-k))=y;
    sn(y,k)=sn(x,1-k),sn(x,1-k)=y;
	push_up(y);
}//旋转
void splay(int x){
    st[tp=1]=x;
    for(int i=x;!check(i);i=fa(i)) st[++tp]=fa(i);
    while(tp) push_down(st[tp--]);
    while(!check(x)){
        int y=fa(x),z=fa(y);
        if(!check(y))
            rotate(chksn(x)!=chksn(y)?x:y);
        rotate(x);
    }push_up(x);
}//splay函数
void access(int x){
    for(int i=0;x;i=x,x=fa(x))
        splay(x),sn(x,1)=i,push_up(x);
}//旋出实链
void mk(int x){
    access(x),splay(x),down(x);
}//换根
int find(int x){
    access(x),splay(x);
    while(sn(x,0)) x=sn(x,0);
    return x;
}//找根
void split(int x,int y){
    mk(x),access(y),splay(y);
}//拉出链
void cut(int x,int y){
    split(x,y),sn(y,0)=fa(x)=0;
}//删边操作
void link(int x,int y){
    mk(x),access(y),fa(x)=y;
}//连边操作
posted @   长安一片月_22  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示