LCT初讲

LCT的全称是Link-Cut-Tree(林克卡特树(滑稽))

听起来好像是个很高大上的数据结构,其实……还好吧,这里一个一个操作分析
首先,要学LCT必须先要有Splay的基础,这里安利一篇blog

LCT主要就是维护树的动态更改(动态树,要求维护动态的森林,解决动态森林上的问题)。

说白了就是可以维护树上路径问题(动态的)

丢一道题目

这题就是LCT的模板题,就是可以在原树上随便断边和连边,更改路径上的点的点权,然后维护一条路径上的最小点权。

先不要着急离开,其实LCT的核心就不到20行,代码长(其实还好)是因为有颗Splay。

首先,为了解决树上路径问题,先要把树划分成树链。

不要想树链剖分(虽然有点像),但是LCT是XJB划分的。

为蛤呢?

因为树是动态的……

也就是划分是动态的(disgusting),不过不怕。。

所以说LCT动态划分要求用可以维护树链动态变化,从任意位置断开,连接等奇奇怪怪的操作,还要能迅速维护树上的信息。

所以最合适的数据结构是:Splay

是不是很有道理(滑稽

首先是树链的划分:

这就是树链的划分,注意,它是根据需要划分的(就是说可以乱搞

然后每颗Splay维护一条树链(就是按照深度维护),然后图上的虚边用Path_Parent来表示,就相当于fa。。

最核心的Access操作,Access操作的目的就是把x连向原树的根,并且x是这条树链的结尾,就是在这条链上深度最深的,说白了就是把x的儿子也断开,看看代码就明白了(最好可以画个图)

代码:

void access(int x){
	int y=0;
	while(x){
		Splay(x);//首先还是要把x转到根(注意是维护这个序列的Splay的根)
		if(ch[x][1]){//然后看右儿子有没有连东西,有就断开,就是把原树结点x一下的在Splay上断开
			fa[ch[x][1]]=0;
			Path_Parent[ch[x][1]]=x;//把Path_Parent连向结点x,就是原树的虚边
		}
		ch[x][1]=y;//连向新的儿子
		if(y) fa[y]=x;//嗯……
		Path_Parent[y]=0;//因为联通了,所以可以不用连Path_Parent
		update(x);//记得更新
		y=x;//继续向上跑
		x=Path_Parent[x];//跑
	}
}

 

好了,这个就是其他很多操作的基础。

下面继续讲其他操作

寻根:找到点x的根(在原树上的)

int getroot(int x){
	access(x);//首先要把x和根连通
	Splay(x);//因为不知道Splay的根,所以要先把x旋转到根(方便处理)
	for(;ch[x][0];x=ch[x][0],pushdown(x)); //因为是按深度排序,所以根肯定是在Splay上最小的(深度最小)
	return x;//返回(原树的根)
}

evert:讲x设为原树的根

void evert(int x){
	access(x); //保证和根联通
	Splay(x);//旋转到根
	rev[x]^=1;//打翻转标记,因为把x设为根就相当于把x到根的这个序列整个翻转
}

link:将两个点连在一起(原树)

void link(int x,int y){
	evert(y);//让其中一个点作为树根
	Splay(y);//然后旋转到根
	Path_Parent[y]=x;//最后……就这样了
}

cut:断边(原树)

void cut(int x,int y){
	evert(x);//将x作为原树树根
	access(y);//把y和树根连通(因为x和y在同一棵树,所以就相当于把x和y连通)
	Splay(y);//转到根
	if(ch[y][0]){//把y的左儿子断开(在Splay上的),因为这是按照深度维护的Splay,所以x的深度肯定比y的小,所以断开y的左子树。
		fa[ch[y][0]]=0;
		ch[y][0]=0;
	}
	update(x);//记得更新
}

ad:把一条路径上的所有的点权+z

void ad(int x,int y,int z){
	evert(x);//还是把x作为树根(原树)
	access(y);//依然是连通y    y->x
	Splay(y);//旋转到根
	add[y]+=z;//然后把这条路径打上标记
}

query:最后询问(套路也一样)

int query(int x,int y){//同上
	evert(x);
	access(y);
	Splay(y);
	return ma[y];
}

没了!!!!

这些就是基本操作,是不是很短,下面贴上完整代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#define mem(x) memset(x,0,sizeof(x))
using namespace std;
const int N = 300005;
int val[N],ma[N],ch[N][2],fa[N],Path_Parent[N],add[N],rev[N];
void init(){//清空用
	mem(val);
	mem(ma);
	mem(ch);
	mem(Path_Parent);
	mem(add);
	mem(rev);
	mem(fa);
}
void pushdown(int x){//下放标记
	if(rev[x]){
		swap(ch[x][0],ch[x][1]);
		if(ch[x][0]) rev[ch[x][0]]^=1;
		if(ch[x][1]) rev[ch[x][1]]^=1;
		rev[x]=0;
	}
	if(add[x]){
		ma[x]+=add[x];
		val[x]+=add[x];
		if(ch[x][0]) add[ch[x][0]]+=add[x];
		if(ch[x][1]) add[ch[x][1]]+=add[x];
		add[x]=0; 
	}
}
void update(int x){//上传标记
	if(!x) return;
	ma[x]=val[x];
	pushdown(ch[x][0]);//我打的是延时标记,所以在上传的时候要先下放
	pushdown(ch[x][1]);
	ma[x]=max(ma[x],max(ma[ch[x][0]],ma[ch[x][1]]));
}
int get(int x){
	return ch[fa[x]][1]==x;
}
void rotate(int x){//Splay的旋转
	int f=fa[x],gf=fa[f],k=get(x);
	if(!f) return;
	if(gf) ch[gf][get(f)]=x;
	fa[x]=gf;
	fa[ch[x][!k]]=f;
	ch[f][k]=ch[x][!k];
	ch[x][!k]=f;
	fa[f]=x;
	Path_Parent[x]=Path_Parent[f];//Path_Parent只用维护在Splay的根就可以了
	Path_Parent[f]=0;
	update(f);
	update(x);
}
int sta[N];
void Splay(int x){
	int now=x,top=0;
	sta[++top]=now;
	while(fa[now]) now=fa[now],sta[++top]=now;
	while(top) pushdown(sta[top--]);  //记得要先下放标记
	for(int f;f=fa[x];rotate(x)) if(get(f)==get(x)&&fa[f]) rotate(f);
	update(x);
}
void access(int x){//以下部分上面已讲
	int y=0;
	while(x){
		Splay(x);
		if(ch[x][1]){
			fa[ch[x][1]]=0;
			Path_Parent[ch[x][1]]=x;
		}
		ch[x][1]=y;
		if(y) fa[y]=x;
		Path_Parent[y]=0;
		update(x);
		y=x;
		x=Path_Parent[x];
	}
}
int getroot(int x){
	access(x);
	Splay(x);
	for(;ch[x][0];x=ch[x][0],pushdown(x)); 
	return x;
}
void evert(int x){
	access(x);
	Splay(x);
	rev[x]^=1;
}
void link(int x,int y){
	evert(y);
	Splay(y);
	Path_Parent[y]=x;
}
void cut(int x,int y){
	evert(x);
	access(y);
	Splay(y);
	if(ch[y][0]){
		fa[ch[y][0]]=0;
		ch[y][0]=0;
	}
	update(x);
}
void ad(int x,int y,int z){
	evert(x);
	access(y);
	Splay(y);
	add[y]+=z;
}
int query(int x,int y){
	evert(x);
	access(y);
	Splay(y);
	return ma[y];
}
int n,u[N],v[N],t;
int main(){
	
	while(~scanf("%d",&n)){
		init();
		for(int i=1;i<n;i++) scanf("%d%d",&u[i],&v[i]);
		for(int i=1;i<=n;i++) scanf("%d",&val[i]),ma[i]=val[i];
		for(int i=1;i<n;i++) link(u[i],v[i]);
		scanf("%d",&t);
		while(t--){
			int c,x,y,z;
			scanf("%d",&c);
			if(c==1){
				scanf("%d%d",&x,&y);
				if(getroot(x)==getroot(y)) {printf("-1\n");continue;}
				link(x,y);
			}
			else
			if(c==2){
				scanf("%d%d",&x,&y);
				if(x==y||getroot(x)!=getroot(y)) {printf("-1\n");continue;}
				cut(x,y);
			}
			else
			if(c==3){
				scanf("%d%d%d",&z,&x,&y);
				if(getroot(x)!=getroot(y)) {printf("-1\n");continue;}
				ad(x,y,z);
			}
			else 
			if(c==4){
				scanf("%d%d",&x,&y);
				if(getroot(x)!=getroot(y)) {printf("-1\n");continue;}
				printf("%d\n",query(x,y));
			}	
		}
		printf("\n");
	}
	
	return 0;
}

还好吧……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值