以后Splay就只记LCT的,其他的用treap
简述
LCT的大概思想是这样的:
每一条重链都是一颗Splay,其中深度为键值(要把握住它是一条连,而且键值不会重复)
同一重链结点之间的连边是通过Splay内部连边完成的
轻边是通过Splay的根结点的父亲连接,但是该父亲不会指向该根结点(著名的儿子认父亲,父亲不认儿子问题)
(Splay的根结点的父亲是Splay在原树中顶端结点的父亲,本来根结点的父亲是0,这里用来存轻边特别合适)
如果不在同一棵树上,那么就父亲儿子都不互相指,这些结点就完全没有关系了
代码
时间复杂度
时间复杂度:均摊应该是O(logn)
注意
- 所有的连接和断边操作都需要pushup一次
- 所有的操作最好先判断一下他们是否在一颗树上
- 所有操作基本上都要基于Splay到根才能进行
- 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到跟结点(不然有可能根本就找不到键值最小的结点),不停往左找,键值最小的就是
Link
把两个结点之间连边
方法:把x弄成树的根,然后直接连y即可,就算是连上了一条轻链
Cut
删除x,y之间的连边,没有连边就删除y和父亲结点的边
如果希望看x,y是否有连边,那么直接用fa似乎就可以查看了,但还是要先make_rt和access才行
方法:把x变成树的跟,然后让y和x疏通(否则可能会断错),把y旋转成跟(否则不好断开),然后和左边的那个结点断开(它的父亲)
延伸操作
初始化
首先每个结点都要你手动赋值的,要赋全!
然后点与点的关系有两个方法可以确定
- 直接用Link连接,时间复杂度多个log,然而我觉得LCT越用越快,所以直接Link也没什么
- 把原树存下来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;

本文介绍了一种高效的树链剖分数据结构——LCT(Link-Cut Tree)。LCT利用Splay树实现动态维护树形结构的功能,支持快速查询和修改树链上的信息。文章详细讲解了LCT的基本概念、核心思想、常用操作及其应用。
2146

被折叠的 条评论
为什么被折叠?



