板子:LCT(现在都不敢说自己写的是学习笔记了)

以后Splay就只记LCT的,其他的用treap

简述

LCT的大概思想是这样的:

每一条重链都是一颗Splay,其中深度为键值(要把握住它是一条连,而且键值不会重复)

同一重链结点之间的连边是通过Splay内部连边完成的

轻边是通过Splay的根结点的父亲连接,但是该父亲不会指向该根结点(著名的儿子认父亲,父亲不认儿子问题)

(Splay的根结点的父亲是Splay在原树中顶端结点的父亲,本来根结点的父亲是0,这里用来存轻边特别合适)

如果不在同一棵树上,那么就父亲儿子都不互相指,这些结点就完全没有关系了

代码

时间复杂度

时间复杂度:均摊应该是O(logn)

注意

  1. 所有的连接和断边操作都需要pushup一次
  2. 所有的操作最好先判断一下他们是否在一颗树上
  3. 所有操作基本上都要基于Splay到根才能进行
  4. Link操作即使用一样的名字也无所谓,它们变量个数不一样

操作汇总

Splay部分
  • pushup
  • pushdown(必须要,不是可有可无)
  • link
  • rot
  • splay
  • bool is_rt(int x):判断x是否是当前Splay的根,因为轻边根的父亲已经不会指向0了
树的基础操作
access:

把x到树的根结点疏通成重链,并且断掉路径上结点的其它重链

操作原理是把x旋转成跟,然后和之前疏通的重链的根结点连接,自然也就断掉了原来的重链,形成新的重链

毕竟重链和轻链之间的差距仅仅是父亲认不认儿子的问题

make_root:

把x变成所在树的根

原来是先疏通,然后把它变成根(这样才能影响所有子树),直接翻转子树即可(即把原
键值大小全部翻转了)

Find操作

找到x所在树的根结点

方法:先把x进行access,然后Splay到跟结点(不然有可能根本就找不到键值最小的结点),不停往左找,键值最小的就是

把两个结点之间连边

方法:把x弄成树的根,然后直接连y即可,就算是连上了一条轻链

Cut

删除x,y之间的连边,没有连边就删除y和父亲结点的边

如果希望看x,y是否有连边,那么直接用fa似乎就可以查看了,但还是要先make_rt和access才行

方法:把x变成树的跟,然后让y和x疏通(否则可能会断错),把y旋转成跟(否则不好断开),然后和左边的那个结点断开(它的父亲)

延伸操作
初始化

首先每个结点都要你手动赋值的,要赋全!
然后点与点的关系有两个方法可以确定

  1. 直接用Link连接,时间复杂度多个log,然而我觉得LCT越用越快,所以直接Link也没什么
  2. 把原树存下来DFS,确定fa的关系,边都是轻边,时间复杂度是O(n)的
对于一条链的修改/查询操作

对于一个树链的修改操作都是一样的:

首先把x弄成树的根,然后疏通y(acc不保证y是根),然后把y旋转成splay的根(只有在根上操作才全面),就可以修改/查询整条链了

似乎也可以不splay最后的那个y,直接把x和它的儿子断了

LCA

听说可以acc(x)然后acc(y)的返回值获得,这也是acc操作为什么要返回值的原因

不过在这之前应该要先确定根才行。

#include<cmath>
#include<queue>
#include<cctype>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=3e5+105;
#define lc ch[now][0]
#define rc ch[now][1]
struct LinkCutTree
{
    //一般来说ch,fa,rev,w都是必须要用的,rev是换根操作要用到,Splay不比线段树可以用其它的参数代替当前结点的值
    int ch[maxn][2],fa[maxn],sz[maxn],add[maxn],mx[maxn],sum[maxn],w[maxn];
    bool rev[maxn];
    void Initial()
    {
        memset(ch,0,sizeof(ch));
        memset(fa,0,sizeof(fa));
        memset(rev,0,sizeof(rev));
        memset(add,0,sizeof(add));
    }
    void pushup(int now)
    {
        sz[now]=1,mx[now]=sum[now]=w[now];//注意这里的清楚操作,后面的操作要求要保证子结点的存在 
        if(lc)
        {
            sz[now]+=sz[lc];
            sum[now]+=sum[lc];
            mx[now]=max(mx[now],mx[lc]);
        }
        if(rc)
        {
            sz[now]+=sz[rc];
            sum[now]+=sum[rc];
            mx[now]=max(mx[now],mx[rc]);
        }
    }
    void pushdown(int now)
    {
        if(add[now])
        {
            if(lc)
            {
                w[lc]+=add[now];
                mx[lc]+=add[now];
                sum[lc]+=sz[lc]*add[now];
                add[lc]+=add[now];
            }
            if(rc)
            {
                w[rc]+=add[now];
                mx[rc]+=add[now];
                sum[rc]+=sz[rc]*add[now];
                add[rc]+=add[now];
            }
            add[now]=0;
        }
        if(rev[now])
        {
            swap(ch[now][0],ch[now][1]);
            if(lc)
                rev[lc]^=rev[now];
            if(rc)
                rev[rc]^=rev[now];
            rev[now]=0;
        }
    }
    bool is_rt(int x)//判断x是否是当前Splay的根,因为轻边跟的父亲已经不会指向0了 
    {
        return ch[fa[x]][0]!=x && ch[fa[x]][1]!=x;
    }
    void link(int i,int d,int j)
    {
        ch[i][d]=j;
        fa[j]=i;
    }
    void rot(int x)
    {
        int y=fa[x],z=fa[y];
        pushdown(y);pushdown(x);//注意pushdown的位置以及顺序 
        int d=(x==ch[y][1]);
        if(!is_rt(y))link(z,ch[z][1]==y,x);
        fa[x]=z;
        //由于Z可能不是当前Splay的结点,如果Z不是,x转成根之后,也应该把fa指向z 
        link(y,d,ch[x][d^1]);
        link(x,d^1,y);
        pushup(y);pushup(x);//x和y是交换过的,所以先y后x 
    }
    void splay(int x)
    {
        pushdown(x);
        while(!is_rt(x))//基本上所有跟结点的判断都必须调用函数 
        {
            int y=fa[x],z=fa[y];
            if(!is_rt(y))(ch[z][0]==y) == (ch[y][0]==x) ? rot(y):rot(x);//用y不是根判断旋转 
            rot(x);
        }
    }

    //---------------------------------------------分界线:上面是Splay,下面是对树的动态维护 
    int acc(int x)//这里不能保证Splay的根,但是可以用来求LCA
    {
        int y=0;
        while(x)
        {
            splay(x);
            ch[x][1]=y;//因为原来的链的键值肯定比当前链的键值大,所以连右儿子即可 
            pushup(x);//连了边之后要pushup 

            y=x;//y记录当前疏通的这条链的根 
            x=fa[x];//x是根,所以fa[x]就是轻边,继续处理 
        }
        return y;
    }
    void mroot(int x)
    {
        acc(x);
        splay(x);
        rev[x]^=1;
    }
    int find(int x)
    {
        acc(x);splay(x);
        while(ch[x][0])
        {
            pushdown(x);
            x=ch[x][0];
        }
        return x;
    }
    bool Link(int x,int y)
    {
        if(find(x)==find(y))return 0;//注意一定要判断是否已经连边 
        mroot(x);fa[x]=y;
        return 1;
    }
    bool Cut(int x,int y)
    {
        if(find(x)!=find(y))return 0;
        mroot(x);acc(y);splay(y);
        fa[ch[y][0]]=0,ch[y][0]=0;
        pushup(y);//同样的需要pushup重新统计
        return 1;
    }

    //----------------------------------------------又一个分界线:再后面就是对于链的修改操作了 
    bool modify(int x,int y,int v)
    {
        if(find(x)!=find(y))return 0;
        mroot(x);acc(y);splay(y);//x即使是树的根但不一定是splay的根
        add[y]+=v,mx[y]+=v,w[y]+=v,sum[y]+=v*sz[y];
        return 1;
    }
    int Max(int x,int y)
    {
        if(find(x)!=find(y))return -1;//当然你得保证原树没有负数,否则换个值吧
        mroot(x);acc(y);splay(y);
        return mx[y];
    }
    int Sum(int x,int y)
    {
        if(find(x)!=find(y))return -1;
        mroot(x);acc(y);splay(y);
        return sum[y];
    }
}lct;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值