Orz 大神 PoPoQQQ的ppt
代码主要学习于Saramanda
开篇
LCT=树链剖分+splay 能解决动态的加边和动态的删边的问题,并且可以维护一条路径上的信息。
LCT分为原树和辅助树,但维护的不是原树而是辅助树
辅助树的每一个节点只记父亲不计儿子 但是每一个节点又有着自己唯一的重儿子,且和重儿子由重边相连,重边构成重链。每一个重链都有splay储存。且splay是把深度作为权值。所以说splay的中序遍历就是这一个重链从上到下的序列。
可能不太清晰,但记住每一个splay代表一条链,且splay符合原树中的权值大小关系。
初始化
struct node
{
int fa,ch[2]; bool reverse,is_root;//父亲 两个儿子 反转标记 是否是splay的根节点。
}T[maxn];
操作
access
LCT中的核心操作,其余的操作基本基于该操作。
access操作是将当前节点x向根节点开通一条重链
方式
将当前的点x splay到该链的根部。x再转到父亲上不断进行该操作直到树根。
void access(int x)
{
int y=0;//y记录上一次操作的点初始化为0
do
{
splay(x);
T[T[x].ch[1]].is_root=true;//因为x以上的节点深度都小于x,所以x的右儿子一定是x以下的节点
T[T[x].ch[1]=y].is_root=false;//上一次的y深度大于x,且连通x与y
//如果维护路径信息 update(x);
x=T[y=x].fa;//让x向上走。
}while(x);
}
mroot
将节点x移到树的树根。
方式
很明显可以将当前的x连通到树根处。 再splay x 到树根。但是因为x是最下部的节点及深度最大。
所以当前的辅助树并不满足x深度最小。 显然的是如果将辅助树倒转,就符合x深度最小。
void mroot(int x)
{
access(x); splay(x); pushreverse(x);
}
标记
如果说要一个个的把每一个节点都反转的话。时间复杂度太高,不能接受。所以可以打一个标记,代表着以该节点为根的子树(不包括根)需要倒转。
void pushreverse(int x)
{
if(!x) return;
swap(T[x].ch[0],T[x].ch[1]);
T[x].reverse^=1;
}
但是如果有了当前的标记的话,在splay的时候又需要把x一直到根的标记 从上向下降下来。
(因为现在的树是倒转的,所以从x向上都需要完成操作,不能有标记)。
void pushdown(int x)
{
if(T[x].reverse)
{
pushreverse(T[x].ch[0]); pushreverse(T[x].ch[1]);//有标记就降给两个儿子
T[x].reverse=false;
}
}
void push(int x)
{
if(!T[x].is_root) push(T[x].fa);//向上找
pushdown(x);//每一个都处理
}
splay
直接上代码
void rotate(int x)
{
int son=getson(x),fa=T[x].fa; int g=T[fa].fa;
T[fa].ch[son]=T[x].ch[son^1]; T[fa].fa=x; T[x].fa=g;
if(T[x].ch[son^1]) T[T[x].ch[son^1]].fa=fa;
T[x].ch[son^1]=fa;
if(!T[fa].is_root) T[g].ch[T[g].ch[1]==fa]=x;
else T[x].is_root=true,T[fa].is_root=false;
//同理 需要维护的话 update(fa); update(x);
}
void splay(int x)
{
push(x);//降掉标记。
for(int fa; !T[x].is_root; rotate(x))
{
if(!T[fa=T[x].fa].is_root) rotate(getson(x)==getson(fa)? fa: x);
}
}
link
连接x,y
方式
将x移到树根 并且将x的父亲赋成y (这是一条轻边)
void link(int x,int y)
{
mroot(x); T[x].fa=y;
}
cut
切掉 x与y之间的边。
方式
将x移到树根,再连通y与x之间的路径,再将y splay到树根,y的左儿子就是x,再切掉边。
证明是反证法 详情请看popoqqq的ppt。
void cut(int x,int y)
{
mroot(x); access(y); splay(y);
T[x].is_root=true; T[x].fa=T[y].ch[0]=0;
}
较完整版代码
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=10000000+10;
struct node
{
int fa,ch[2];
bool reverse,is_root;
}T[maxn];
int getson(int x);
void pushreverse(int x);
void pushdown(int x);
void push(int x);
void rotate(int x);
void splay(int x);
void access(int x);
void mroot(int x);
void link(int u,int v);
void cut(int u,int v);
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
return 0;
}
int getson(int x)
{
return x==T[T[x].fa].ch[1];
}
void pushreverse(int x)
{
if(!x) return;
swap(T[x].ch[0],T[x].ch[1]);
T[x].reverse^=1;
}
void pushdown(int x)
{
if(T[x].reverse)
{
pushreverse(T[x].ch[0]); pushreverse(T[x].ch[1]); T[x].reverse=false;
}
}
void push(int x)
{
if(!T[x].is_root) push(T[x].fa);
pushdown(x);
}
void rotate(int x)
{
int k=getson(x),fa=T[x].fa;int g=T[fa].fa;
T[fa].ch[k]=T[x].ch[k^1];
if(T[x].ch[k^1]) T[T[x].ch[k^1]].fa=fa;
T[x].ch[k^1]=fa; T[fa].fa=x; T[x].fa=g;
if(!T[fa].is_root) T[g].ch[fa==T[g].ch[1]]=x;
else T[x].is_root=true,T[fa].is_root=false;
}
void splay(int x)
{
push(x);
for(int fa; !T[x].is_root; rotate(x))
{
if(!T[fa=T[x].fa].is_root)
{
rotate((getson(x)==getson(fa))?fa:x);
}
}
}
void access(int x)
{
int y=0;
do
{
splay(x);
T[T[x].ch[1]].is_root=true;
T[T[x].ch[1]=y].is_root=false;
x=T[y=x].fa;
}while(x);
}
void mroot(int x)
{
access(x); splay(x); pushreverse(x);
}
void link(int u,int v)
{
mroot(u); T[u].fa=v;
}
void cut(int u,int v)
{
mroot(u); access(v); splay(v);
T[T[v].ch[0]].is_root=1;
T[u].fa=T[v].ch[0]=0;
}
做题的话看饕餮传奇’blog。
尾言
每一种数据结构的学习都离不开大量的练习。 欢迎dalao斧正