树链剖分(1) 树剖求LCA

先看一个树剖的经典应用:

初始化:

先DFS一遍子树,统计出每一个点x的重儿子son[x]和以x为根节点的子树的大小siz[x],这里选择x中子树大小最大的儿子作为它的重儿子,第二遍dfs划分树链:重儿子与其父亲节点划分到一条链。其他的儿子为x的轻儿子,但属于新的链的顶端元素。

void dfs1(int x,int father,int dep)//主要统计siz和son 
{
	fa[x]=father,deep[x]=dep,siz[x]=1;
	for(ri k=fst[x];k>0;k=nxt[k])
		if(v[k]!=father)
		{
			dfs1(v[k],x,dep+1);
			if(siz[v[k]]>cur[x])	son[x]=v[k],cur[x]=siz[v[k]];//寻找子树大小最大的儿子作为x的重儿子 
			siz[x]+=siz[v[k]];
		}
}

void dfs2(int x,int anc)//划分树链
//anc:点x所属的链的顶端节点 
{
	top[x]=anc;//top[x]记录点x所在的链的顶端元素的序号。此即x的重儿子和x属于一条重链 
	if(son[x]!=0)	dfs2(son[x],anc);//防止死循 
	for(ri k=fst[x];k>0;k=nxt[k])
		if(v[k]!=fa[x]&&v[k]!=son[x])	dfs2(v[k],v[k]);
}

初始完之后如何求LCA(x,y):

当x和y不属于一条链时,将所属链的顶端节点深度更大(deep[top[]]值更大的)的点向上提。直到x和y属于一条链为止。此时它们的LCA就是x和y中深度更浅的那个。

int LCA(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(deep[top[x]]<=deep[top[y]])	{ y=fa[top[y]]; continue; }
		if(deep[top[x]]>deep[top[y]])	  { x=fa[top[x]]; continue; }
	}
	if(deep[x]>=deep[y])	return y;
	return x;
}

这样我们就实现完了树剖求LCA。

完整代码(P3379 【模板】最近公共祖先(LCA)):

#include<cstdio>
#include<iostream>
#define ri register int
using namespace std;

const int MAXN=1000020;
int n,q,s,m,u[MAXN],v[MAXN],fst[MAXN],nxt[MAXN],xi,yi;
int fa[MAXN],deep[MAXN],siz[MAXN],cur[MAXN],son[MAXN],top[MAXN];

void dfs1(int x,int father,int dep)
{
    fa[x]=father,deep[x]=dep,siz[x]=1;
    for(ri k=fst[x];k>0;k=nxt[k])
        if(v[k]!=father)
        {
            dfs1(v[k],x,dep+1);
            if(siz[v[k]]>cur[x])	son[x]=v[k],cur[x]=siz[v[k]];
            siz[x]+=siz[v[k]];
        }
}

void dfs2(int x,int anc)
{
    top[x]=anc;
    if(son[x]!=0)	dfs2(son[x],anc);
    for(ri k=fst[x];k>0;k=nxt[k])
        if(v[k]!=fa[x]&&v[k]!=son[x])	dfs2(v[k],v[k]);
}

int LCA(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(deep[top[x]]<=deep[top[y]])	{ y=fa[top[y]]; continue; }
        if(deep[top[x]]>deep[top[y]])	  { x=fa[top[x]]; continue; }
    }
    if(deep[x]>=deep[y])	return y;
    return x;
}

int main()
{
    scanf("%d%d%d",&n,&q,&s);
    m=(n-1)<<1;
    for(ri i=1;i<=m;i+=2)
    {
        scanf("%d%d",&u[i],&v[i]);
        nxt[i]=fst[u[i]],fst[u[i]]=i;
        u[i+1]=v[i],v[i+1]=u[i];
        nxt[i+1]=fst[u[i+1]],fst[u[i+1]]=i+1;
    }
    dfs1(s,s,0); 
    dfs2(s,s);
    for(ri i=1;i<=q;i++)
    {
        scanf("%d%d",&xi,&yi);
        cout<<LCA(xi,yi)<<'\n';
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值