LCT 动态树(实链剖分)

基础篇: 简介概念和模板

进阶篇: 只有题目

模板分析: 来自 ac wing

刷题记录: pt 23.2

问题描述

  • 查询、修改链上的信息(最值,总和等)
  • 随意指定原树的根(即换根)
  • 动态连边、删边
  • 合并两棵树、分离一棵树
  • 动态维护连通性

细化步骤

重点提醒:
1,子节点不会跳出当前节点所在的实链:
虚链链接的时候,父亲不知道儿子是谁,但是儿子知道父亲

2,子节点发生变动的节点需要up,自顶向下递归子节点的时候需要down

3,Up 或者 Down一个不能少, LCT 由于特别灵活的原因,少 down 或 up 一次就可能把修改改到不该改的点上!

4,LCT 的 Rotate 和 Splay 的不太一样,if (z) 一定要放在前面。

5,LCT 的 Splay 操作就是旋转到根,没有旋转到谁儿子的操作,因为不需要。

splay部分

1, spin

只须在对z的儿子进行操作时注意一点:
y若是实链顶部(root),z的子节点不能发生改变

void spin(int x)
{
    int y= tr[x].p;
    int z= tr[y].p;
    int k= tr[y].s[1]==x;
    if(!isroot(y))tr[z].s[tr[z].s[1]==y]=x;
    tr[x].p=z;
    tr[y].s[k]=tr[x].s[k^1]; tr[tr[x].s[k^1]].p=y;
    tr[x].s[k^1]=y; tr[y].p=x;
    up(y); up(x);
}

2, splay

以往 splay 找某个节点的时候,都是从根节点往下找(按照 BST 的性质)

但是在 LCT 中,splay 充当的是辅助树的角色,我们获得 splay 中的节点是通过原树中对应节点的编号

换而言之,我们是直接获得 splay 中的某个节点,而不是自上而下递归找到的

所以,在做 splay 转到根节点的旋转操作时,我们需要先 自上而下懒标记 下传

void splay (int x)
{
    int top = 0;
    int r  = x;
    stk[++top]=x;
    while (!isroot(r))stk[++top]=r=tr[r].p;
    while (top) down(stk[top--]);
    
    while (!isroot(x))
    {
        int y= tr[x].p;
        int z= tr[y].p;
    
        if(!isroot(y))
        {
            if((tr[y].s[1]==x) ^ (tr[z].s[1]==y))spin(x);
            else spin(y);
        }
        spin(x);
    }
}

LCT 部分

子节点发生变动的节点需要up
自顶向下递归子节点的时候需要down

1, access 定位

将x到根(实际的树)的路径修改为实边,并把x转到根(保证splay复杂度)

void access(int x)
{
    int z =x;
    for (int y= 0; x; y=x,x=tr[x].p)
    {
        splay(x);
        tr[x].s[1]=y;
        up(x);     改接树上结构需要up
    }
    splay(z);      access自带把x转上辅助树的树根
}

2, cut 断边

cut的条件:
1,y 所在的原树中的根是否是 x
2,y 的父节点是否是根 x
3,y 是否有左孩子(中序遍历紧挨在 x 的后面)

void cut(int x,int y)
{
    make_root(x);
    if(find_root(y)==x && tr[y].p==x && !tr[y].s[0])
    {
        tr[x].s[1]=tr[y].p=0;
        up(x);
    }
}

3, 其他操作

int find_root(int x)/// 辅助树的root
{
    access(x);
    while (tr[x].s[0])
    {
        down(x); ///忘记2
        x=tr[x].s[0];
    }
    splay(x);
    return x;
}

void make_root(int x)/// 把x做成原树的根节点,需要反转整个中序遍历
{
    access(x);
    push_rev(x);///q1
}

void sign(int x,int y) ///把x到y的路径标记为实边
{
    make_root(x);
    access(y);
}

void link(int x,int y)
{
    make_root(x);
    if(find_root(y)!=x )tr[x].p=y;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值