LCT
解决动态树问题的一种数据结构
推荐 PoPoQQQ 的详解
链上求和
链上求最值
链上修改
断开树上一条边 *
连接两个点 *
概念
- 重儿子(暂时这样命名): 一个节点最多只能有一个重儿子
- 重边: 连接父亲节点和重儿子的边
- 重链: 由重边和重边连接的节点构成的链
- 轻边: 除了重边以外的边
重点
- 类比树链剖分,每条重链都用线段树维护信息, LCT用splay来维护(因为它的结构在不断变化)
- splay中每个节点的键值是它的深度(不用单独记录和比较)
- 一条重链对应一个splay树,不同重链的splay相互分离
- splay的树根不代表原树的树根,它只是splay树的一个节点
- splay就是一条重链,重链的顶部的父节点记录在splay的根上
- 每个点的记录的父节点不意味着原树的父节点(除了每个splay根记录的)
- splay的中序遍历就是重链从顶部到底部的序列
- LCT的根在不停的变化
各种操作
access
功能: 切掉与重儿子的重边,建立一条通向原树根的重链
用途: 形成当前点到原树根的重链的splay,快速维护链上信息
- 先将x旋转到splay的根部
- 将它的重儿子换成新的重儿子,也就是上一个重链的顶部
- 重复操作它的父节点
void access(int x) {
for(int y=0; x; y=x, x=fa(x)){
splay(x);
//现在x的重儿子一定是它的rson,证明 在后面
rson(x)=y; update(x);
}
}
y表示上一个操作的x
makeRoot
功能: 将x变成原树根
用途: 方便的找到x->y的链(先将x变为根,再access(y))
- 先让x与原树根联通(access(x))
- splay(x) 让它转到根部
- reverse
reverse之前
reverse之后
也就是反转操作,保证在splay中相对位置的正确(交换每一个节点的左右儿子)
这里是标记,在splay时要降标记
void reverse(int x) {
if(!x) return;
swap(lson(x), rson(x)); T[x].reverse^=1;
}
void makeRoot(int x) {
access(x); splay(x); reverse(x);
}
link
功能:连接x->y
- 将x变成原树根
- 将x的父节点记为y
void link(int x, int y) {
if(findRoot(x)==findRoot(y)) return;
makeRoot(x); fa(x)=y;
}
cut
功能: 切掉x->y的连边
- 先将x变为原树根
- 再access(y),将x与y连在一起(此时splay中只有两个节点)
- splay(y), 此时 y 的左儿子就是x,证明在后面
- 清空x的父节点,y的右儿子
void cut(int x, int y) {
makeRoot(x); access(y); splay(y);
fa(x)=lson(y)=0;
}
splay部分
int getson(int x) { return x==rson(fa(x)); }
bool is_root(int x) { return lson(fa(x))!=x && rson(fa(x))!=x; }//判断是否为当前splay的根
void update(int x) { ... }
void reverse(int x) {
if(!x) return;
swap(lson(x), rson(x)); T[x].reverse^=1;
}
void downFlag(int x) {
if(!is_root(x)) downFlag(fa(x));
if(T[x].reverse){
reverse(lson(x)); reverse(rson(x));
T[x].reverse=0;
}
}
void rotate(int x) {
int fa=fa(x), gfa=fa(fa), wh=getson(x);
if(!is_root(fa)) T[gfa].ch[getson(fa)]=x; fa(x)=gfa;
T[fa].ch[wh]=T[x].ch[wh^1]; T[T[fa].ch[wh]].fa=fa;
T[x].ch[wh^1]=fa; T[fa].fa=x;
update(fa); update(x);
}
void splay(int x) {
downFlag(x);
for(; !is_root(x); rotate(x))
if(!is_root(fa(x)))
rotate(getson(fa(x))==getson(x)?fa(x):x);
}
PoPoQQQ
由于找节点并非自上至下,故操作之前需预先将节点到splay根的标记全下传一遍
由于父节点未必和当前节点在同一棵Splay中,所以要判断是不是父亲节点的儿子(is_root)
注:证明部分
PoPoQQQ
反证法
假设y进行Access+Splay操作时通过双旋将x旋转到y的左儿子的左儿子,那么x和y之间一定有一个节点,故x,y之间深度差不为1,与已知x,y之间有连边矛盾
模板题
冗长代码
/*
[HNOI2010]Bounce 弹飞绵羊
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define File(x) "bzoj_2002."#x
#define For(i,s,e) for(int i=(s); i<=(e); i++)
#define Rep(i,s,e) for(int i=(s); i>=(e); i--)
using namespace std;
const int N=200000+3;
struct LCT{
int ch[2],fa,size;
bool reverse;
#define lson(x) T[x].ch[0]
#define rson(x) T[x].ch[1]
#define fa(x) T[x].fa
}T[N];
int n,k[N],m,t;
bool is_root(int x) { return lson(fa(x))!=x && rson(fa(x))!=x; }
int getson(int x) { return x==rson(fa(x)); }
void update(int x) { T[x].size=T[lson(x)].size+T[rson(x)].size+1; }
void reverse(int x) {
if(!x) return;
swap(lson(x), rson(x)); T[x].reverse^=1;
}
void downFlag(int x) {
if(!is_root(x)) downFlag(fa(x));
if(T[x].reverse){
reverse(lson(x)); reverse(rson(x));
T[x].reverse=0;
}
}
void rotate(int x) {
int wh=getson(x), fa=fa(x), gfa=fa(fa);
if(!is_root(fa)) T[gfa].ch[getson(fa)]=x; fa(x)=gfa;
T[fa].ch[wh]=T[x].ch[wh^1]; T[T[fa].ch[wh]].fa=fa;
T[x].ch[wh^1]=fa; fa(fa)=x;
update(fa); update(x);
}
void splay(int x) {
downFlag(x);
for(; !is_root(x); rotate(x))
if(!is_root(fa(x)))
rotate((getson(fa(x))==getson(x))?fa(x):x);
}
void access(int x) {
for(int y=0; x; y=x, x=fa(x)){
splay(x); rson(x)=y; update(x);
}
}
void mroot(int x) {
access(x); splay(x); reverse(x);
}
void link(int x, int y) {
mroot(x); fa(x)=y;
}
void cut(int x, int y) {
mroot(x); access(y); splay(y);
lson(y)=fa(x)=0;
}
int Ask(int x) {
mroot(t); access(x); splay(x);
return T[x].size;
}
int main()
{
freopen(File(in),"r",stdin);
freopen(File(out),"w",stdout);
// ios::sync_with_stdio(false);
scanf("%d",&n);
t=n+1;//添加虚拟根,方便查询
For(i,1,n) scanf("%d",&k[i]);
For(i,1,n){
if(i+k[i]<=n) T[i].fa=i+k[i];
else T[i].fa=t;
}
scanf("%d",&m);
while(m--){
int x,y,opt;
scanf("%d%d",&opt,&x);
x++;//注意下标
if(opt==1){
printf("%d\n",Ask(x)-1);//减去虚拟根
}
if(opt==2){
scanf("%d",&y);
if(x+k[x]<=n) cut(x,x+k[x]);
else cut(x,t);
k[x]=y;
if(x+k[x]<=n) link(x,x+k[x]);
else link(x,t);
}
}
return 0;
}
希望各位DaLao指点