首页 > 其他分享 >最近公共祖先 树链剖分

最近公共祖先 树链剖分

时间:2023-05-03 21:22:25浏览次数:43  
标签:结点 chain 剖分 祖先 top 树链 int read 重链

例题:洛谷P3379 【模板】最近公共祖先(LCA)
https://www.luogu.com.cn/problem/P3379

首先是几个概念
重儿子:父结点所有子树中最大的子树的根节点(只有一个或没有)
轻儿子:父结点除了重儿子以外的所有子结点(没有或有很多个)
重边:父结点和重儿子连的边
轻边:父结点和轻儿子连的边
重链:相接重边连成的路径

定理:
1.一棵树能被剖分成若干条重链
2.轻儿子一定是重链的顶点(轻儿子自己也可以成一“条”重链)
3.任意一条路径都能被切分成不超过logn条重链(也是时间复杂度的依据)

时间复杂度是O(n+mlogn),不过用起来似乎比tarjan快?

初始化完后基本思路就是让u,v跳到同一条重链,谁在上谁就是lca

#include<iostream>
#include<vector>
#define forup(i,l,r) for(int i=l;i<=r;i++)
#define fordown(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
 
const int N=5e5+5;
vector<int> child[N];
int fa[N],dep[N];
int heavy_child[N],size_tree[N],chain_top[N];//分别是该节点的重儿子,以该节点为根的子树的结点数,该节点所在重链的顶点 
inline int read()
{
	int n=0;
	char m=getchar();
	while(m<'0'||m>'9') m=getchar();
	while(m>='0'&&m<='9') {
		n=(n<<1)+(n<<3)+(m^'0');
		m=getchar();
	}
	return n;
}
void dfs_1(int father,int u)//初始化fa,dep,heavy_child,size(其实就是用来推重儿子)数组 
{
	fa[u]=father;
	dep[u]=dep[father]+1;
	size_tree[u]=1;
	for(int v:child[u])
	{
		if(v==father) continue;
		dfs_1(u,v);
		size_tree[u]+=size_tree[v];//u结点为根的树的大小就是其所有子树大小之和 
		if(size_tree[heavy_child[u]]<size_tree[v]) heavy_child[u]=v;//同步更新重儿子 
	}
}
void dfs_2(int top,int u)//初始化top数组 
{
	//先找重儿子、重边
	chain_top[u]=top;
	if(child[u].empty()) return;//一直到叶结点 
	dfs_2(top,heavy_child[u]);//继续找重儿子 
	//重儿子找过了就再找轻儿子、轻边,相当于是再建了一个重链,因为每条重链的顶点都是轻儿子 
	for(int v:child[u])
	{
		if(v!=fa[u]&&v!=heavy_child[u])//除去重儿子和父结点其他结点就是轻儿子了 
		{
			dfs_2(v,v);
		}
	}
}
int lca(int u,int v)
{
	while(chain_top[u]!=chain_top[v])//到同一条重链上 
	{
		if(dep[chain_top[u]]>=dep[chain_top[v]]) u=fa[chain_top[u]];//让深度小的先跳,保证跳到重链上离两点最近的公共祖先 
		else v=fa[chain_top[v]];
	}
	return dep[u]<dep[v]?u:v;//已经到同一条重链上,谁在上面谁就是LCA
}
int main()
{
	int n,m,root;
	cin>>n>>m>>root;
	int u,v;
	forup(i,1,n-1)
	{
		u=read(),v=read();
		child[u].push_back(v);
		child[v].push_back(u);
	}
	dfs_1(0,root);
	dfs_2(root,root);
	forup(i,1,m)
	{
		u=read(),v=read();
		cout<<lca(u,v)<<endl;
	}
	return 0;
}

参考的代码lca函数循环里面原本是下面这个,swap操作是不是会慢一点呢,反正上面那个好明白一点点吧

	if(dep[chain_top[u]]<dep[chain_top[v]]) swap(u,v);
	u=fa[chain_top[u]];

参考:https://www.cnblogs.com/dx123/p/16320467.html

标签:结点,chain,剖分,祖先,top,树链,int,read,重链
From: https://www.cnblogs.com/V-sama/p/17369701.html

相关文章

  • 最近公共祖先 Tarjan算法
    例题:洛谷P3379【模板】最近公共祖先(LCA)https://www.luogu.com.cn/problem/P3379tarjan算法是利用了并查集来求LCA的,时间复杂度比倍增低,是O(n+m)#include<iostream>#include<vector>#include<utility>#defineforup(i,l,r)for(inti=l;i<=r;i++)#definefordown(i,l,r)fo......
  • 最近公共祖先
    倍增求LCA①初始化:通过bfs初始化两个数组depth[],fa[]\(\quad\)\(\quad\)depth[n]:表示深度(到根节点的距离加1)\(\quad\)\(\quad\)fa[i][j]:表示从i开始,向上走\(2^j\)步所能到的节点编号(\(0\leqslantj\leqslantlog_2n\))\(\quad\)\(\quad\)\(......
  • 「学习笔记」tarjan求最近公共祖先
    Tarjan算法是一种离线算法,需要使用并查集记录某个结点的祖先结点。并没有传说中的那么快。过程将询问都记录下来,将它们建成正向边和反向边。在dfs的过程中,给走过的节点打上标记,同时维护并查集,这里利用了回溯的思想,如果\(u\)节点的这棵子树没搜完,那么fa[u]=u;,搜完后......
  • LCA(最近公共祖先)学习笔记
    前言没想到干完lca的时间比tarjan的还要长(我不能这么弱下去了!!)前置知识dfs序这东西有点类似于时间戳(dfn),但是它分为两部分(回溯之前和回溯之后)。并且dfs序还分为两种。这里只介绍一倍的dfs序。如上图,蓝色代表左端点,红色代表右端点,(学过Tarjan的都知道),蓝色其实就是这棵树的dfn(......
  • 【学习笔记】长链剖分
    简述在常规树链剖分中把重儿子设成\(siz\)最大的儿子,这样从根跳重链时子树大小至少减半,因此只需要\(O(\logn)\)次即可到达任何节点。考虑把关键字由\(siz\)改成子树内最大的深度\(dep\),这样的剖分方法称为长链剖分。voiddfs1(intu,intfa,intd){dep[u]=d,mxdep......
  • 长链剖分学习笔记
    一些定义重子节点表示其子节点中子树深度最大的子结点如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。轻子节点表示剩余的子结点从这个结点到重子节点的边为重边到其他轻子节点的边为轻边若干条首尾衔接的重边构成重链把落单的结点也当作重链,那么整棵......
  • 节点与其祖先之间的最大差值(树,二叉树,深度优先搜索)
    1、节点与其祖先之间的最大差值(难度中等)给定二叉树的根节点root,找出存在于不同节点A和B之间的最大值V,其中V=|A.val-B.val|,且A是B的祖先。(如果A的任何子节点之一为B,或者A的任何子节点是B的祖先,那么我们认为A是B的祖先)/***Definitionforabinary......
  • LightOJ 1348 Aladdin and the Return Journey (树链剖分)
    树链剖分模板题。最近一直有比赛。。好长时间没写了。明显生疏了。。找个模板题熟悉一下。代码如下:#include<iostream>#include<string.h>#include<math.h>#include<queue>#include<algorithm>#include<stdlib.h>#include<map>#include<set>#include......
  • BZOJ 2243 [SDOI2011] 染色 (树链剖分)
    题目地址:BZOJ2243普通的树链剖分,用线段树维护区间段数与最左边和最右边的颜色。然后当合并区间的时候判断一下左儿子的右端与右儿子的左端是否相同,若相同,则将和减去1.同样,在迭代求值的过程中,也要记录下上条链的最顶端的颜色。代码如下:#include<iostream>#include<strin......
  • SPOJ 375 QTREE系列-Query on a tree (树链剖分)
    题目地址:SPOJ375树链剖分第一发!果然是个貌似很高级的数据结构,其实就是把树的边从树形结构转化成了线性结构,从而可以用线段树或树状数组之类的数据结构进行快速维护。从而将时间缩到n*log(2*n).这题用的线段树维护的。代码如下:#include<iostream>#include<string.h......