关于 Link Cut Tree 的一些说明

前言

和上次的splay一样,把你们的Independence开起来,今天我们再嗨一次!
学习Link Cut Tree需要一定的splay的基础,如果你的基础还是像我一样不够扎实,请点击上面的链接,学习一下 splay s p l a y 再走.
这个博客真是命运多舛.写完了突然卡了发布不了,然后刷新一下又变回原来这样子了.

背景

想必大家都知道LCT指的是动态树了.

原理

首先它到底是维护什么东西的呢?
大家肯定是学过树链剖分的.
树链剖分是将树按照重儿子剖成一条一条的链,也被称为重链剖分.
而LCT是实链剖分.
所谓实链剖分,是对于每一个节点将它和它某一个儿子的边划为实边,和其他儿子的边都划为虚边.
而虚实边是在不断地变化之中的,因此不能使用线段树来维护,而应该使用splay.
LCT维护的对象是一个森林,借助实链剖分,它可以支持的操作多到无法想象.
它有几条性质:
1.每一个splay是维护一条链,链上不能有两个深度相同的点,并且中序遍历每一棵splay得到的点在原树中的深度是严格递增的.
2.每一个节点仅仅包含于一棵splay中.
3.实边包含在splay中,虚边从一棵splay指向另一个节点(该splay中中序遍历最靠前的节点在原树中的父亲.)
注意被虚边指向的点不能沿着虚边访问回去,即认父不认子.

操作

access

Link Cut Tree 中最重要的一个操作,是打通某节点到根的路径,也即将该节点与根放进同一棵splay.
明显此时在原树中从根节点到该节点的边全部变为实边,与路径上的点无关的其它点之间的边相对变为虚边.
那么操作顺序是这样的.

1.设当前被操作点为x,则先把x splay到当前splay的根.
2.把x的右儿子设为y.
3.更新x的信息.
4.让y=x,将x变为x虚边指向的父亲.

所以代码如下.只需要一个for循环.

void access(int x){
  for (int y=0;x;y=x,x=fa[x])
    splay(x),rs(x)=y,push_up(x);
  }

make_root

仅仅是将根和某个点之间的路径拉出来并没有什么用处.现在我们需要两个节点之间的路径的信息.,
然而发生路径不能满足按照深度严格递增的情况,由于性质1,这样的路径不能产生在一个splay中.
这个时候我们要利用accesssplay两个操作.
先把 x x access一下,然后splay到根.
这个时候思考一下人生.
此时的x是splay里面深度最大的点,没有右子树.我们只要把整棵树颠倒过来,就可以使 x x 没有左子树,从而变成深度最小的点,也就是根.

int rev(int x){
  swap(ls(x),rs(x)),tag[x]^=1;//交换左右儿子并打标记
  }
void mkrt(int x){
  access(x),splay(x),rev(x);
  }

find_root

可以找到x所代表原树的树根,用来判断两点间的连通性.
先把 x x access一下,再splay到根,不断地找它的左儿子,就会找到深度最小的节点,也就是根.

void push_down(int x){
  if (tag[x]) rev(ls(x)),rev(rs(x)),tag[x]=0;
  }
int fdrt(int x){
  access(x),splay(x);
  for (;ls(x);x=ls(x)) push_down(x);//注意push_down
  return x;
  }

split

现在我们可以make_root,我们可以利用上面的工具直接访问x,y两点在树上的路径.

void split(int x,int y){
  mkrt(x),access(y),splay(y);
  }
//此时直接调取y的信息就可以了.

连一条 xy x → y 的边.
x x 变成root之后,判断 y y x是否已经连接.
如果find_root(y)=x说明 x,y x , y 已经连接,直接返回.

int link(int x,int y){
  mkrt(x);
  return fdrt(y)^x?fa[x]=y,1:0; 
  }

cut

断开 x,y x , y 之间的边.
如何判断 x,y x , y 之间是否已经连边?
首先它们必须要直接或者间接连通.
所以find(y)=x.
如果 y y x没有直接的父子关系,必然能找到一些点,它的中序遍历在 x,y x , y 之间.
所以fa[x]=y.
然而即使 y y x的父亲, x x 也不能有右子树.
x若存在右子树,它右子树中的点的中序遍历也是在 x,y x , y 之间的.
所以rs(x)=0.
接下来将fa[x]ls(y)清空.显然 x x 一定是y的左儿子.

int cut(int x,int y){
  mkrt(x);
  if (fdrt(y)!=x||fa[x]^y||rs(x)) return 0;
  return fa[x]=ls(y)=0,1;
  }

代码实现

洛谷 p3690 模板 动态树

#include<bits/stdc++.h> //Ithea Myse Valgulious
namespace chtholly{
typedef long long ll;
#define re0 register int
#define rec register char
#define rel register ll
#define gc getchar
#define pc putchar
#define p32 pc(' ')
#define pl puts("")
/*By Citrus*/
inline int read(){
  int x=0,f=1;char c=gc();
  for (;!isdigit(c);c=gc()) f^=c=='-';
  for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
  return f?x:-x;
  }
template <typename mitsuha>
inline bool read(mitsuha &x){
  x=0;int f=1;char c=gc();
  for (;!isdigit(c)&&~c;c=gc()) f^=c=='-';
  if (!~c) return 0;
  for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
  return x=f?x:-x,1;
  }
template <typename mitsuha>
inline int write(mitsuha x){
  if (!x) return 0&pc(48);
  if (x<0) x=-x,pc('-');
  int bit[20],i,p=0;
  for (;x;x/=10) bit[++p]=x%10;
  for (i=p;i;--i) pc(bit[i]+48);
  return 0;
  }
inline char fuhao(){
  char c=gc();
  for (;isspace(c);c=gc());
  return c;
  }
}using namespace chtholly;
using namespace std;
const int yuzu=3e5;
typedef int fuko[yuzu|10];
int n,m;

struct link_cut_tree{
fuko fa,ch[2],tag,sum,val;
#define ls(x) ch[0][x]
#define rs(x) ch[1][x]
#define ws(x,y) (rs(x)==y)
int nrt(int x){return ls(fa[x])==x||rs(fa[x])==x;}// not_root
int rev(int x){swap(ls(x),rs(x)),tag[x]^=1;}
void push_down(int x){if (tag[x]) rev(ls(x)),rev(rs(x)),tag[x]=0;}
void push_up(int x){sum[x]=sum[ls(x)]^sum[rs(x)]^val[x];}
void zhuan(int x){
  int y=fa[x],z=fa[y],k=ws(y,x),ps=ch[!k][x];
  if (nrt(y)) ch[ws(z,y)][z]=x;
  ch[!k][x]=y,ch[k][y]=ps;
  if (ps) fa[ps]=y;
  fa[y]=x,fa[x]=z,push_up(y);
  }
void pushall(int x){
  if (nrt(x)) pushall(fa[x]);
  push_down(x);
  }
int splay(int x){
  pushall(x);
  for (;nrt(x);zhuan(x)){
    int y=fa[x],z=fa[y];
    if (nrt(y)) zhuan(ws(y,x)^ws(z,y)?x:y);
    }push_up(x);
  }
int access(int x){for (int y=0;x;y=x,x=fa[x]) splay(x),rs(x)=y,push_up(x);}
void mkrt(int x){access(x),splay(x),rev(x);}
void split(int x,int y){mkrt(x),access(y),splay(y);}
int fdrt(int x){
  access(x),splay(x);
  for (;ls(x);x=ls(x)) push_down(x);
  return x;
  }
int link(int x,int y){
  mkrt(x);
  return fdrt(y)^x?fa[x]=y,1:0;
  }
int cut(int x,int y){
  mkrt(x);
  if (fdrt(y)!=x||(fa[x]^y)||rs(x)) return 0;
  return fa[x]=ls(y)=0,push_up(y),1;
  }
}my_;
#define split my_.split
#define link my_.link
#define cut my_.cut
#define val my_.val
#define splay my_.splay
#define sum my_.sum
int main(){
n=read(),m=read();
for (int i=1;i<=n;++i) val[i]=read();
for (;m--;){
  int op=read(),x=read(),y=read();
  switch(op){
    case 0: split(x,y),write(sum[y]),pl; break;
    /*把x,y开到同一个splay里,直接取y的值就可以了.*/
    case 1: link(x,y); break;
    case 2: cut(x,y); break;
    case 3: splay(x),val[x]=y; break;
    /*先把x转到根,然后再改变val,不会影响父亲的值.*/
    }
  }
}

谢谢大家.

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值