芝士:LCT
Posted loney-s
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了芝士:LCT相关的知识,希望对你有一定的参考价值。
目录
背景
树链剖分只能解决静态的树上的问题,
但是对于动态的树上问题,树链剖分就凉了,LCT成为首选
虽然不知道为什么是先发明的LCT,再出现的树链剖分
主要思想
为什么树链剖分的时间复杂度小?
因为对于一堆点可以直接维护,
LCT也是一样的道理
用splay维护一堆点,splay之间相连的边称之为虚边,splay内部的边成为实边
splay的左子树上的点是他的祖先,右子树上的点是他的后代
特别说明,splay中每个点的深度是一定不一样的
操作
access
思路
将当前的点u到根节点路径上的所有的点成为一个splay上的点,
看着这个操作十分复杂,但是实际上很简单
因为我们对整棵树长成什么样子并不在意,
我们在意的只是splay之间的连接
所以我们只要将u号节点转到当前splay的根
我们定义u号节点的父亲为v
虽然v的右子树是他的后代,但是他的右子树是一定不包含v
因为他们不在一颗splay中,并且splay中的点的深度一定是互不相同的
所以我们直接将v的右子树断掉,再将u接上去就行了
就这样一直反复就行了
最开始以u号节点为根的splay的右子树也要断去
代码
void access(int u)
{
int v=0;
while(u)
{
splay(u);
tre[u].ch[1]=v;
push_up(u);
v=u;
u=tre[u].fa;
}
}
makeroot
思路
将原树的根换到指定节点
有了access操作,这个操作也很简单
根的左子树一定是空的
所以我们先将当前节点与根节点打通
在splay一下就行了
但是因为根换了,所以需要将整颗splay打上反转的懒标记
代码
void makeroot(int u)
{
access(u);
splay(u);
update_rev(u);
}
findroot
思路
找当前节点属于哪一棵原树(因为断边操作可以使其成为一个森林)
通过access操作,
一定能将当前点u和根节点打通,
但是access操作并不能保证根节点一定在splay的根节点
所以我们需要将u转到根节点,再一直往左儿子走就行了
代码
int findroot(int u)
{
access(u);
splay(u);
while(tre[u].ch[0])
{
push_down(u);
u=tre[u].ch[0];
}
splay(u);
return u;
}
link
思路
连一条u,v的边
用makeroot操作使u号节点成为根节点
再将u号节点和v号节点连一条虚边就行了
需要特判原本是否已经再一颗树上
代码
void link(int u,int v)
{
makeroot(u);
if(findroot(v)==u)
return;
tre[u].fa=v;
}
split
思路
将u和v成为在一条路径上的点
用makeroot和access操作可以很方便的实现
这里默认makeroot(u)
代码
void merge(int u,int v)
{
makeroot(u);
access(v);
splay(v);
}
cut
思路
将u,v之间的边断去
先用split操作使u和v成为一条路径上的点
因为u和v之间原本是直接连边的
所以在路径上u和v是相连的,
随便将一个旋转到根之后,断去splay上的边上即可
但是这是保证操作合法情况
如果是不合法?
也就是u和v之间本来没有边
首先最容易想到的是u和v是否在一颗树上
如果合法,u和v的深度差一定是等于1的
先split(u,v)
也就是说u的右儿子如果不是v,那么就一定是不合法情况
代码
void cut(int u,int v)
{
makeroot(u);
if(findroot(v)!=u||tre[v].fa!=u||tre[v].ch[0])
return;
tre[v].fa=0;
tre[u].ch[1]=0;
splay(u);
}
splay
注意一下,因为我们涉及到splay的分裂与合并,所以在splay的时候就要懒标记下穿
void rotate(int x)
{
int y=tre[x].fa;
int z=tre[y].fa;
int k=tre[y].ch[1]==x;
if(!isroot(y))
tre[z].ch[tre[z].ch[1]==y]=x;
tre[x].fa=z;
tre[y].ch[k]=tre[x].ch[k^1];
tre[tre[x].ch[k^1]].fa=y;
tre[x].ch[k^1]=y;
tre[y].fa=x;
push_up(y);
push_up(x);
}
void splay(int x)
{
down(x);
while(!isroot(x))
{
int y=tre[x].fa;
int z=tre[y].fa;
if(!isroot(y))
{
if((tre[z].ch[0]==y)^(tre[y].ch[0]==x))
rotate(x);
else
rotate(y);
}
rotate(x);
}
push_up(x);
}
例题
题目
这里以洛谷的P3690为例
代码
#include<iostream>
#include<cstdio>
using namespace std;
struct link_cut_tree
{
#define MAXN 100005
struct Splay
{
int ch[2];
int fa;
int val;
int s;
bool lazy_rev;
}tre[MAXN];
int cnt;
#undef MAXN
bool isroot(int u)
{
return tre[tre[u].fa].ch[0]!=u&&tre[tre[u].fa].ch[1]!=u;
}
int newnode(int val,int fa)
{
cnt++;
tre[cnt].ch[0]=tre[cnt].ch[1]=0;
tre[cnt].fa=0;
tre[cnt].val=val;
tre[cnt].s=val;
tre[cnt].lazy_rev=0;
return cnt;
}
void update_rev(int k)
{
tre[k].lazy_rev^=1;
swap(tre[k].ch[0],tre[k].ch[1]);
}
void push_down(int k)
{
if(k==0)
return;
if(tre[k].lazy_rev)
{
tre[k].lazy_rev=0;
update_rev(tre[k].ch[0]);
update_rev(tre[k].ch[1]);
}
}
void push_up(int k)
{
if(k==0)
return;
tre[k].s=tre[k].val^tre[tre[k].ch[0]].s^tre[tre[k].ch[1]].s;
}
void rotate(int x)
{
int y=tre[x].fa;
int z=tre[y].fa;
int k=tre[y].ch[1]==x;
if(!isroot(y))
tre[z].ch[tre[z].ch[1]==y]=x;
tre[x].fa=z;
tre[y].ch[k]=tre[x].ch[k^1];
tre[tre[x].ch[k^1]].fa=y;
tre[x].ch[k^1]=y;
tre[y].fa=x;
push_up(y);
push_up(x);
}
void down(int u)
{
if(!isroot(u))
down(tre[u].fa);
push_down(u);
}
void splay(int x)
{
down(x);
while(!isroot(x))
{
int y=tre[x].fa;
int z=tre[y].fa;
if(!isroot(y))
{
if((tre[z].ch[0]==y)^(tre[y].ch[0]==x))
rotate(x);
else
rotate(y);
}
rotate(x);
}
push_up(x);
}
void access(int u)
{
int v=0;
while(u)
{
splay(u);
tre[u].ch[1]=v;
push_up(u);
v=u;
u=tre[u].fa;
}
}
void makeroot(int u)
{
access(u);
splay(u);
update_rev(u);
}
int findroot(int u)
{
access(u);
splay(u);
while(tre[u].ch[0])
{
push_down(u);
u=tre[u].ch[0];
}
splay(u);
return u;
}
void merge(int u,int v)
{
makeroot(u);
access(v);
splay(v);
}
void link(int u,int v)
{
makeroot(u);
if(findroot(v)==u)
return;
tre[u].fa=v;
}
void cut(int u,int v)
{
makeroot(u);
if(findroot(v)!=u||tre[v].fa!=u||tre[v].ch[0])
return;
tre[v].fa=0;
tre[u].ch[1]=0;
splay(u);
}
int ask_sum(int u,int v)
{
merge(u,v);
splay(u);
return tre[u].s;
}
void change(int u,int x)
{
splay(u);
tre[u].val=x;
splay(u);
return;
}
}tre;
int n,m;
int opt;
int u,v;
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
tre.newnode(x,0);
}
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&opt,&u,&v);
if(opt==0)
{
printf("%d
",tre.ask_sum(u,v));
}
if(opt==1)
{
tre.link(u,v);
}
if(opt==2)
{
tre.cut(u,v);
}
if(opt==3)
{
tre.change(u,v);
}
}
return 0;
}
以上是关于芝士:LCT的主要内容,如果未能解决你的问题,请参考以下文章