首页 > 其他分享 >最近公共祖先 RMQ

最近公共祖先 RMQ

时间:2023-05-05 22:11:24浏览次数:40  
标签:lg 结点 RMQ 祖先 深度 int 公共 序列 forup

就是把LCA问题转化为RMQ问题
转化之前先要了解欧拉序列:对一棵树进行 DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为 2n-1 的序列,这个序列被称作这棵树的欧拉序列。
比如下面这个树:(2连3和4)
1->2->3
        ->4->5
其序列就是1 2 3 2 4 5 4 2 1,共九个

记录下每个结点第一次出现的位置,也就是1 2 3 5 6
和树链剖分的思路有点类似,对于给出的u,v,两点间深度最小的点就是LCA
只不过这里两点间也就是欧拉序列包含这两点的这段区间

也记下序列对应的深度,1 2 3 2 3 4 3 2 1

那么用st表记下2^k区间内深度最小的结点号,查询时比较深度即可

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

#include<iostream>
#include<vector>
#include<cmath>
#define forup(i,l,r) for(int i=l;i<=r;i++)
using namespace std;
const int N=5e5+5;
vector<int> child[N];//结点连接的结点(包含父结点) 
int dep[N];//深度 
int st[2*N][20];//i开始2^k区间中深度最小的点 
int pos[N];//第一次出现的位置 
int lg[2*N];
int tot;//序列中的编号 
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(int father,int u)
{
	dep[u]=dep[father]+1;
	st[++tot][0]=u;
	if(!pos[u]) pos[u]=tot;//存第一次出现的位置 
	for(int x:child[u]) 
	{
		if(x!=father) 
		{
			dfs(u,x);
			st[++tot][0]=u;//回溯也要记入序列 
		}
	}
}
void ST()
{
	int p=lg[tot];
	forup(k,1,p)
	{
		forup(i,1,tot-(1<<k)+1)
		{
			if(dep[st[i][k-1]]<dep[st[i+(1<<(k-1))][k-1]]) st[i][k]=st[i][k-1];//取深度较小的编号 
			else st[i][k]=st[i+(1<<(k-1))][k-1];
		}
	}
}
int query(int l,int r)
{
	if(l>r) swap(l,r);//区间嘛,l<=r 
	int p=lg[r-l+1];
	if(dep[st[l][p]]<dep[st[r-(1<<p)+1][p]]) return st[l][p];
	else return st[r-(1<<p)+1][p];
}
int main()
{
	int n,m,root;
	cin>>n>>m>>root;
	forup(i,1,n-1)
	{
		int u=read(),v=read();
		child[u].push_back(v);
		child[v].push_back(u);
	}
	dfs(0,root);//求欧拉序列 
	forup(i,2,tot) lg[i]=lg[i/2]+1;//初始化lg数组 
	ST();//建st表 
	forup(i,1,m)
	{
		cout<<query(pos[read()],pos[read()])<<endl;
	}
	return 0;
}

当然也可以用pair或结构体让st同时存深度和编号,看个人喜好

参考:https://blog.csdn.net/Wyt_code/article/details/83903164

标签:lg,结点,RMQ,祖先,深度,int,公共,序列,forup
From: https://www.cnblogs.com/V-sama/p/17375464.html

相关文章

  • 最近公共祖先 算法总结
    现知LCA算法有倍增、Tarjan、树链剖分再LCA问题上各自的特点如下表倍增Tarjan树链剖分数组fa[u][i],depfa,vis,query,ansfa,dep,size_tree,heavy_child,top_chain思路深搜打表,跳跃查询深搜回溯,离线查询二重深搜打表,跳跃查询时间复杂度O((n+m)lo......
  • 最近公共祖先 树链剖分
    例题:洛谷P3379【模板】最近公共祖先(LCA)https://www.luogu.com.cn/problem/P3379首先是几个概念重儿子:父结点所有子树中最大的子树的根节点(只有一个或没有)轻儿子:父结点除了重儿子以外的所有子结点(没有或有很多个)重边:父结点和重儿子连的边轻边:父结点和轻儿子连的边重链:相接......
  • 最近公共祖先 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;,搜完后......
  • 两个链表的第一个公共结点
    使用空间存储节点的解法classSolution{public:set<ListNode*>s;ListNode*findFirstCommonNode(ListNode*headA,ListNode*headB){for(autoi=headA;i;i=i->next)s.insert(i);for(autoi=headB;i;i=i->next)......
  • 【LeetCode动态规划#14】子序列系列题(最长递增子序列、最长连续递增序列、最长重复子
    最长递增子序列力扣题目链接(opensnewwindow)给你一个整数数组nums,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]是数组[0,3,1,6,2,2,7]的子序列。示例1:输入:nums=[10,9,2,5,3,7......
  • JS逆向实战13——某市公共资源交易中心Cookie混淆加密
    "本文地址:https://www.cnblogs.com/zichliang/p/17346860.html目标网站aHR0cDovL2xkZ2d6eS5obmxvdWRpLmdvdi5jbi9sZGp5engvanl4eC9saXN0LnNodG1s网站分析经过浏览器抓包,我们可知这个网站会先发出一个412请求,然后附带一个js然后返回正常的页面。经过我们代码测试可知如......
  • LCA(最近公共祖先)学习笔记
    前言没想到干完lca的时间比tarjan的还要长(我不能这么弱下去了!!)前置知识dfs序这东西有点类似于时间戳(dfn),但是它分为两部分(回溯之前和回溯之后)。并且dfs序还分为两种。这里只介绍一倍的dfs序。如上图,蓝色代表左端点,红色代表右端点,(学过Tarjan的都知道),蓝色其实就是这棵树的dfn(......
  • [Leetcode]找出链表公共结点
    力扣链接思路:先求出两个链表的长度差长链表先走差距步同时走,第一个地址相同的是交点代码:/***Definitionforsingly-linkedlist.*structListNode{*intval;*structListNode*next;*};*/structListNode*getIntersectionNode(structListNode*he......