树链剖分求LCA

树链剖分


深夜写博客……


LCA的问题是可以有很多方法解出来的了,比如Tarjan,或者转化成ST表的RMQ问题,或者在树上倍增跳Ancestors数组Blablabla……。

今天总结用树链剖分去求LCA,一种最费代码又最慢的做法。

为什么有其它优秀的算法不用呢?它的优点在于空间复杂度,是N,而ST表和Anc倍增都是NlogN的。如丁某某出题人要卡你的空间,出个8MB的话,你的空间就炸了,这时候只有取下下策,树链剖分。

树链剖分是什么?

想出这个东西的人值得佩服。对于一棵二叉树来说,它就像一个天平一样,每个根(包括子树根)就是天平的中间的那根针,而每一个子节点可以想象成砝码。而这些砝码的尺度又是由什么规定的呢?子树的大小。而树链剖分干的事情就是将每个根或者子树根的那个重的砝码挑出来,重的砝码叫做重儿子。所有的重砝码的位置有可能不连续,但是一定有个数大于等于1的重砝码连在一起,而这些连起来的重砝码的叫做重链。而那些拥有砝码数量相对轻的叫做轻边。轻边一定是连接两条重链的,因为无论如何轻边所连接的下一个点一定链接着重链。

重链有可能是一个点……当儿子的轻重程度相同时,随便选哪一个都行……

关键字

dfs1,siz[ ]:dfs1 维护siz[ ],siz[ ]用来判断哪一个才是重儿子;

dfs2,seq[ ],dep[ ],in[ ],out[ ],top[ ]:dfs2 维护 :1、dep[ ],记录每一个节点的深度;

                                                                                            2、seq[ ],表示的是这个特殊的dfs序每个位置上的结点编号;

                                                                                            3、in[ ],out[ ],记录了每个节点在这个dfs序上进出时间                                                                                                         时间用idc记录

                                                                                            4、top[ ],表示每个结点沿着重链一步步往更浅的点跳最高跳                                                                                                    到哪里;

求LCA的原理:

由于每个点都在一条重链或者重儿子上,所以我们要找的点一定也在这些重链上;由于dfs序上的每一条重链上的每一个点是连续的,这也是为何这个dfs序是特殊的了,我们可以在dfs序上找到这条链:[ top[u] , in[u] ](当然,从in[u]到top[u]深度单调减)。设要找LCA的两个点的编号分别为u,v:

那么u和v深度较深的一定可以从in[u]跳到father[ top[u] ],那么他就可以跳到另一条重链最底下的那个点,接着又跳相对来说浅的那个深的。一直这样进行下去,最终一定可以跳到同一条重链上来(最坏情况是跳到根节点)。当u'和v'在同一条重链时,深度较浅的一定是他们的LCA!

是不是很神奇?做了这么多准备工作只是为了跳远!

这就是链剖;

 md我联训的时候没有听懂,一定是ZJC讲得太烂了…… 

代码如下:

#include<bits/stdc++.h>

#define RG rigister

using namespace std;
const int N=500000+5,P=20;

int to[N<<2],head[N<<2],nxt[N<<2];
int dep[N<<2],fa[N<<2],siz[N<<2],in[N<<1],out[N<<1],son[N<<1],top[N<<1];
int n,x,m,a,b,root,cn,idc,Lans;

void create(int x,int y){
	cn++;
	to[cn]=y;
	nxt[cn]=head[x];
	head[x]=cn;
}
void dfs1(int u){
	int v;
	siz[u]=1;
	for(int i=head[u];i;i=nxt[i]){
		v=to[i];
		if(v==fa[u]) continue;
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
} 
void dfs2(int u,int tp){
	int v;
	in[u]=++idc;
	top[u]=tp;
	if(son[u]) dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i]){
		v=to[i];
		if(v==fa[u] || v==son[u]) continue;
		dfs2(v,v);
	}
	out[u]=idc;
}
int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) u^=v^=u^=v;
		u=fa[top[u]];
	}
	return dep[u]<dep[v] ? u : v ;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
	    scanf("%d",&a);
	    if(a==0)
	        root=i;
	    create(a,i);
	    create(i,a);
	}
	dep[root]=1;
	fa[root]=root;
	dfs1(root);
	dfs2(root,root);
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&a,&b);
		a=a^Lans;b=b^Lans;
		Lans=LCA(a,b);
		printf("%d\n",Lans);
	}
	return 0;
}

下面这玩意儿支持链、子树修改和链、子树查询。(树链剖分+线段树)

#include<bits/stdc++.h>
using namespace std;

const int N=100000+5;

int head[N<<2],to[N<<2],nxt[N<<2];
long long sum[N<<2],tar[N<<2];
int seq[N<<2],in[N<<2],out[N<<2],siz[N<<2],top[N<<2],fa[N<<2],dep[N<<2],son[N<<2],idc;
int flag[N<<2],g[N<<2];
int n,m,a,b,cn,ok;

void create(int u,int v){
	cn++;
	to[cn]=v;
	nxt[cn]=head[u];
	head[u]=cn;
}
void pushdown(int o,int l,int r){
	if(flag[o]){
		int mid=(l+r)>>1;
		sum[o<<1]+=1LL*tar[o]*(mid-l+1);
		sum[o<<1|1]+=1LL*tar[o]*(r-mid);
		tar[o<<1]+=tar[o];
		tar[o<<1|1]+=tar[o];
		flag[o<<1]=flag[o<<1|1]=1;
		flag[o]=tar[o]=0;
	}
}
void dfs1(int u){
	int v;
	siz[u]=1;
	for(int i=head[u];i;i=nxt[i]){
		v=to[i];
		if(v==fa[u]) continue;
		dep[v]=dep[u]+1;
		fa[v]=u;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}
void dfs2(int u,int tp){
	int v;
	in[u]=++idc;
	seq[idc]=u;
	top[u]=tp;
	if(son[u]) dfs2(son[u],tp);
	for(int i=head[u];i;i=nxt[i]){
		v=to[i];
		if(v==fa[u] || v==son[u]) continue;
		dfs2(v,v);
	}
	out[u]=idc;
}
void modify(int o,int l,int r,const int L,const int R,const int val){
	if(L<=l && r<=R){
		sum[o]+=1LL*val*(r-l+1);
		tar[o]+=val;
		flag[o]=1;
		return ;
	}
	int mid=(l+r)>>1;
	pushdown(o,l,r);
	if(L<=mid) modify(o<<1,l,mid,L,R,val);
	if(R>mid) modify(o<<1|1,mid+1,r,L,R,val);
	sum[o]=sum[o<<1]+sum[o<<1|1];
}
long long query(int o,int l,int r,const int L,const int R){
	if(L<=l && r<=R)
		return sum[o];
	int mid=(l+r)>>1;
	pushdown(o,l,r);
	long long cn=0;
	if(L<=mid) cn+=query(o<<1,l,mid,L,R);
	if(R>mid) cn+=query(o<<1|1,mid+1,r,L,R);
	return cn;
}
long long wans_query(int u,int v){
	long long ans=0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) u^=v^=u^=v;
		ans+=query(1,1,n,in[top[u]],in[u]);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) u^=v^=u^=v;
	ans+=query(1,1,n,in[v],in[u]);
	return ans;
}
void wans_modify(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) u^=v^=u^=v;
		modify(1,1,n,in[top[u]],in[u],1);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) u^=v^=u^=v;
	modify(1,1,n,in[v],in[u],1);
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n-1;i++){
		cin>>a>>b;
		create(a,b);
		create(b,a);
	}
	fa[1]=1;
	dep[1]=1;
	dfs1(1);
	dfs2(1,1);
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>ok;
		if(ok==1){
			cin>>a>>b;
			wans_modify(a,b);
		}
		else{
			cin>>a>>b;
			cout<<wans_query(a,b)<<endl;
		}
	}
	return 0;
}
情人节,窝在老家过了。唉,真难受啊……

加油吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值