总算学会了...NOIp2018之前就开始学了...模板一直过不去...
需要先学会Splay。
现在切入正题。
LCT的概况
一种数据结构。(废话)
可以均摊(O(logn))维护一个森林,支持的树上任意路径的查询,两棵树的连接、断开,单点的修改。
LCT的实现基于Splay。
大概长成下面这样↓
(图片来自这位dalao的博客,下同)
LCT中的边分为实边和虚边,每一个节点连向它的孩子的边中只有一条实边。
实边需要记录在这条边的两个端点上(即孩子记父亲,父亲记孩子),虚边只需要记录在孩子上(即孩子记父亲即可)。
其中实边(实线的边)连接的一组点构成一个Splay,大小依据为深度。
Splay的根的父亲指针指向这棵Splay中深度最小的节点在原树中的父亲(这是一条虚边)。
比如上面这个LCT的Splay长这样(当然和Splay的形态没关系)↓
具体操作
LCT中的Splay必须维护的是翻转标记,其他的视题目而定。
首先是一个前置操作,判断点(y)是否是Splay的根(因为即使是Splay的根也可能有在别的Splay中有父亲,所以并不是没有父亲的才是根):
bool Isroot(int x){
int y=t[x].f;
return !(t[y].c[0]==x||t[y].c[1]==x);
}
本文的Splay节点定义如下:
struct node{
int f,c[2],tag,...;
//f是父亲,c[0/1]是左/右孩子,tag是翻转标记
}t[Size];
Splay的操作有些会有所修改,如下:
上/下传(并没有不一样):
void Upd(int x){//视具体情况而定
//something...
}
void Psd(int x){//主要是下传翻转标记
if(t[x].tag){
swap(t[x].c[0],t[x].c[1]);
if(t[x].c[0])t[t[x].c[0]].tag^=1;
if(t[x].c[1])t[t[x].c[1]].tag^=1;
t[x].tag=0;
}
}
旋转:
void Rotate(int x){
int y=t[x].f,z=t[y].f;
Psd(y);
Psd(x);
int c=t[y].c[1]==x;
if(!Isroot(y)){//注意这里要判断y是否为Splay的根(否则z可能在另一颗Splay中或者不存在)
int gc=t[z].c[1]==y;
t[z].c[gc]=x;
}
t[x].f=z;
t[y].c[c]=t[x].c[c^1];
t[t[x].c[c^1]].f=y;
t[x].c[c^1]=y;
t[y].f=x;
Upd(y);
Upd(x);
}
伸展:
void Splay(int x){
Psd(x);
while(!Isroot(x)){//这里同上
int y=t[x].f,z=t[y].f;
if(Isroot(y))Rotate(x);
else{
Psd(z);
Psd(y);
int c=t[y].c[1]==x,gc=t[z].c[1]==y;
if(c==gc)Rotate(y);
else Rotate(x);
Rotate(x);
}
}
}
接下来终于开始讲LCT了
1.访问操作(Access(x))
使(x)与当前LCT的根存在由实边组成的路径,并且(x)是路径中深度最大的点。
之后所有操作的基础。
我们从(x)开始,一路向父亲寻找,每次将节点伸展为所在Splay的根,将该节点的父亲的所有连向儿子的边变成虚边,将该节点的父亲和节点本身的边变成实边(该节点是其父亲的右儿子,因为深度大于它的父亲)。
当然为了保证(x)是深度最大的,一开始要把(x)的右儿子断开。
我个人喜欢递归实现,调用方式为(Access(x,0))。
code:
void Access(int x,int lst){
if(!x)return;
Splay(x);
t[x].c[1]=lst;
Upd(x);
Access(t[x].f,x);
}
2.换根操作(Makeroot(x))
将(x)换为LCT的根。
我们需要(Access(x)),此时(x)与LCT的根直接相连,而且是它到根的路径上深度最大的。
我们要让它变成根,就是路径中深度最小的点,怎么办?
直接翻转Splay不就好了。。。
为了确定翻转标记标记的位置,我们再将(x)换为所在Splay的根。
code:
void Makeroot(int x){
Access(x,0);
Splay(x);
t[x].tag^=1;
}
3.??操作(Split(x,y))
难以概括的操作。。。就是将(x)到(y)的路径提取出来方便询问。
将(x)换为LCT的根,再(Access(y)),就得到了(x)到(y)的路径。
再(Splay(y)),就可以在(y)节点上获取路径信息了。
code:
void Split(int x,int y){
Makeroot(x);
Access(y,0);
Splay(y);
}
4.找根操作(Findroot(x))
找到(x)所在LCT的根,主要用于判断两点联通性。
先(Access(x)),将(x)和它在LCT中的根放在同一个Splay中,再(Splay(x)),将(x)变为所在Splay的根。
然后(x)所在LCT的根必然是深度最小的,所以一直跳左孩子即可。
(记得下传标记哦)
code:
int Findroot(int x){
Access(x,0);
Splay(x);
Psd(x);
while(t[x].c[0]){
x=t[x].c[0];
Psd(x);
}
Splay(x);//维持Splay的平衡
return x;
}
5.连接操作(Link(x,y))
在(x,y)之间连一条边。
首先如果(x,y)已经联通((Findroot(x)=Findroot(y))),操作不合法,直接退出即可。
否则,让(x)成为所在LCT的根,将它和(y)之间连一条虚边(即让(x)的父亲为(y))。
code:
void Link(int x,int y){
Makeroot(x);
if(Findroot(y)!=x)t[x].f=y;
}
6.断边操作(Cut(x,y))
断开(x)和(y)之间的边(如果有的话)。
(Makeroot(x))后,(x)是LCT的根,而(y)深度必然大于(x),所以会在右子树中,深度相差1(如果存在这条边),即在Splay中(x)的右孩子。
如果(y)和(x)直接连边,那么首先判断联通性,然后看看(y)是否是(x)的右孩子,并且(y)没有左孩子(如果有左孩子说明(x)和(y)之间还有点)。
存在的话直接断边就好了。
code:
void Cut(int x,int y){
Makeroot(x);
Psd(x);
if(Findroot(y)==x&&t[y].f==x&&!t[y].c[0]){
t[x].c[1]=t[y].f=0;
Upd(x);
}
}
结束。
最后给出模板题代码:
#include<bits/stdc++.h>
using namespace std;
struct node{
int f,c[2],v,xs,tag;
}t[300010];
int n,m,op,u,v;
void PrintLCT(){
for(int i=1;i<=n;i++){
cout<<t[i].f<<"<-node "<<i<<" l->"<<t[i].c[0]<<" r->"<<t[i].c[1]<<"
";
}
}
bool Isroot(int x){
int y=t[x].f;
return !(t[y].c[0]==x||t[y].c[1]==x);
}
void Upd(int x){
t[x].xs=t[x].v^t[t[x].c[0]].xs^t[t[x].c[1]].xs;
}
void Psd(int x){
if(t[x].tag){
swap(t[x].c[0],t[x].c[1]);
if(t[x].c[0])t[t[x].c[0]].tag^=1;
if(t[x].c[1])t[t[x].c[1]].tag^=1;
t[x].tag=0;
}
}
void Rotate(int x){
int y=t[x].f,z=t[y].f;
Psd(y);
Psd(x);
int c=t[y].c[1]==x;
if(!Isroot(y)){
int gc=t[z].c[1]==y;
t[z].c[gc]=x;
}
t[x].f=z;
t[y].c[c]=t[x].c[c^1];
t[t[x].c[c^1]].f=y;
t[x].c[c^1]=y;
t[y].f=x;
Upd(y);
Upd(x);
}
void Splay(int x){
Psd(x);
while(!Isroot(x)){
int y=t[x].f,z=t[y].f;
if(Isroot(y))Rotate(x);
else{
Psd(z);
Psd(y);
int c=t[y].c[1]==x,gc=t[z].c[1]==y;
if(c==gc)Rotate(y);
else Rotate(x);
Rotate(x);
}
}
}
void Access(int x,int lst){
if(!x)return;
Splay(x);
t[x].c[1]=lst;
Upd(x);
Access(t[x].f,x);
}
void Makeroot(int x){
Access(x,0);
Splay(x);
t[x].tag^=1;
}
void Split(int x,int y){
Makeroot(x);
Access(y,0);
Splay(y);
}
int Findroot(int x){
Access(x,0);
Splay(x);
Psd(x);
while(t[x].c[0]){
x=t[x].c[0];
Psd(x);
}
Splay(x);
return x;
}
void Link(int x,int y){
Makeroot(x);
if(Findroot(y)!=x)t[x].f=y;
}
void Cut(int x,int y){
Makeroot(x);
Psd(x);
if(Findroot(y)==x&&t[y].f==x&&!t[y].c[0]){
t[x].c[1]=t[y].f=0;
Upd(x);
}
}
int Query(int x,int y){
Split(x,y);
return t[y].xs;
}
void Change(int x,int y){
Splay(x);
t[x].v=y;
Upd(x);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&t[i].v);
t[i].xs=t[i].v;
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&op,&u,&v);
if(op==0)printf("%d
",Query(u,v));
else if(op==1)Link(u,v);
else if(op==2)Cut(u,v);
else Change(u,v);
}
return 0;
}