求最近公共祖先(LCA)的各种算法
水一发题解。
我只是想存一下树剖LCA的代码......
以洛谷上的这个模板为例:P3379 【模板】最近公共祖先(LCA)
1.朴素LCA
就像做模拟题一样,先dfs找到基本信息:每个节点的父亲、深度。
把深的节点先往上跳。
深度相同了之后,一起往上跳。
最后跳到一起了就是LCA了。
预处理:O(n)
每次查询:O(n)
2.倍增LCA
朴素LCA的一种优化。
一点一点跳,显然太慢了。
如果要跳x次,可以把x转换为二进制。
每一位都是1或0,也就是跳或者不跳。
在第i位,如果跳,就向上跳2(i-1)次。
至于跳或者不跳,判断很简单。
如果跳了之后还没在一起,就跳。
预处理:算出每个点上跳2n次后的位置。(已知上跳20次的位置就是它的父亲)O(nlogn)
每次询问:O(logn)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; int n,m,s;
int hd[],nx[],to[],cnt; void add(int af,int at)
{
to[++cnt]=at;
nx[cnt]=hd[af];
hd[af]=cnt;
} int d[],f[][]; void pre(int p,int fa)
{
f[p][]=fa;
d[p]=d[fa]+;
for(int i=hd[p];i;i=nx[i])
{
if(to[i]!=fa)pre(to[i],p);
}
} int lca(int x,int y)
{
if(d[x]<d[y])swap(x,y);
for(int i=;i>=;i--)
{
if(d[f[x][i]]>=d[y])x=f[x][i];
}
if(x==y)return x;
for(int i=;i>=;i--)
{
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
}
return f[x][];
} int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=;i<n;i++)
{
int aa,bb;
scanf("%d%d",&aa,&bb);
add(aa,bb);
add(bb,aa);
}
pre(s,);
for(int i=;i<=;i++)
{
for(int j=;j<=n;j++)
{
f[j][i]=f[f[j][i-]][i-];
}
}
for(int i=;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return ;
}
倍增LCA
3.欧拉序+RMQ
欧拉序,就是dfs时,无论是进入该点的子树,还是从该点的子树中出来,都记录一遍这个点。这样得到一个序列,就是欧拉序。
比如说点A为根,BCD为A的儿子的一颗简单的树,加上一个E作为C的儿子。
其欧拉序就是A B A C E C A D A
那么,任取两点,它们的LCA,就是欧拉序中,这两个点之间深度最小的点。
如果一个点在欧拉序中出现了多次,任取一个位置就好。
区间深度最小点,用RMQ。O(nlogn)预处理后,每次询问O(1)求出。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; int m,n,ecnt,root;
int head[],nx[],to[];
int euler[],eucnt,ps[],high[][];
int fa[],dep[];
int log[]; int add(int af,int at)
{
to[++ecnt]=at;
nx[ecnt]=head[af];
head[af]=ecnt;
} void dfs(int pos,int fat)
{
dep[pos]=dep[fat]+;
euler[++eucnt]=pos;
ps[pos]=eucnt;
fa[pos]=fat;
for(int i=head[pos];i;i=nx[i])
{
if(to[i]!=fat)
{
dfs(to[i],pos);
euler[++eucnt]=pos;
}
}
} void prelca()
{
for(int i=;i<=*n;i++)log[i]=log[i/]+;
for(int i=;i<=eucnt;i++)high[i][]=euler[i];
for(int i=;i<=;i++)
{
for(int j=;j+(<<i)-<=eucnt;j++)
{
if(dep[high[j][i-]]>dep[high[j+(<<(i-))][i-]])
high[j][i]=high[j+(<<(i-))][i-];
else
high[j][i]=high[j][i-];
}
}
} int lca(int x,int y)
{
int ll=ps[x];
int rr=ps[y];
if(ll>rr)int t=ll; ll=rr; rr=t;
int len=rr-ll+;
if(dep[high[ll][log[len]]]>dep[high[rr-(<<log[len])+][log[len]]])
return high[rr-(<<log[len])+][log[len]];
else
return high[ll][log[len]];
} int main()
{
scanf("%d%d%d",&n,&m,&root);
for(int i=;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
dfs(root,);
prelca();
for(int i=;i<=m;i++)
{
int q,w;
scanf("%d%d",&q,&w);
printf("%d\n",lca(q,w));
}
return ;
}
欧拉序+RMQ
4.树链剖分
把树分成轻链和重链。
先一遍dfs找到重儿子,即子树最大的儿子。
每个点与重儿子的连边组成重链。
第二遍dfs记录每个点的tp值:所在重链的顶端。
如果在轻链上,tp就是它自己。
求LCA;类似倍增。
让tp较深的点上跳,跳到fa[tp]。
最后tp[x]==tp[y]的时候,二者在同一重链上,LCA即为深度较浅的那个点。
预处理:O(n)
每次询问:O(logn)
#include<cstdio> int hd[],to[],nx[],cnt;
int hs[],tp[],f[],d[],sz[]; int n,m,s; void add(int af,int at)
{
to[++cnt]=at;
nx[cnt]=hd[af];
hd[af]=cnt;
} void dfs(int p,int fa)
{
f[p]=fa;
d[p]=d[fa]+;
sz[p]=;
for(int i=hd[p];i;i=nx[i])
{
if(to[i]==fa)continue;
dfs(to[i],p);
sz[p]+=sz[to[i]];
if(sz[to[i]]>sz[hs[p]])hs[p]=to[i];
}
} void findtp(int p)
{
if(p==hs[f[p]])tp[p]=tp[f[p]];
else tp[p]=p;
for(int i=hd[p];i;i=nx[i])
if(to[i]!=f[p])findtp(to[i]);
} int lca(int a,int b)
{
while(tp[a]!=tp[b])d[tp[a]]>d[tp[b]]?a=f[tp[a]]:b=f[tp[b]];
return d[a]<d[b]?a:b;
} int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(s,);
findtp(s);
for(int i=;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));
}
return ;
}
树链剖分
5.离线tarjan
(待填坑)
6.欧拉序+约束RMQ
洛谷上的玄学操作。应该是欧拉序+RMQ的优化。
把原欧拉序分块,块内预处理,块间ST表。(我并不知道ST表是什么......)
摘自洛谷题解:
分块大小定为L=log(n)/2,这样共分D=n/L块,对这D个数(块内最小值)做正常ST表,建表复杂度O(Dlog(D))=O((n/L)(log(n)-log(L))=O(n)
我们要保证每个步骤都是O(n)的,log(n)/2的块正好消去了ST建表时的log
但在此之前,我们得处理出块内的最小值,该怎么做呢?一个正常想法就是枚举每个数,一共是O(n)复杂度
但是,这样做虽然留下了每块的最小值以及其取到的位置,若考虑查询块的一个区间,而这个区间恰好取不到最小值,这时候只能暴力枚举,就破坏了查询O(1)了
至此我们仍没有使用其±1的特殊性质,现在考虑一下。
块内一共log(n)/2个数,由乘法原理可知,本质不同的块有U=2^(log(n)/2)=n^(1/2)个,我们不妨处理出每个这种块,复杂度Ulog(n)/2,这个函数增长是小于线性的,可以认为是O(n)
这样,处理出每个块内两元素的大小关系,就可以用01唯一表示一个块了,可以用二进制存下来,作为一个块的特征,这一步复杂度O(n)
这样有一个好处,即使查询块内一个区间,我们只需要提取这个区间对应的二进制数,就可以在预处理的数组中O(1)查询了
(怎么做呢?把这段二进制数提出来,移到最右边,由于我们规定0表示小于,1表示大于,所以会贪心地选取前面的数,查表减去偏移量就可以了)
查询时,类似分块,边角的块直接查表,中间部分ST表查询,查询是O(1)的。
至此我们完成了O(n)建表,O(1)查询的约束RMQ。
一般地,对于任何一个序列,可以在O(n)时间内建成一颗笛卡尔树,把查询该序列RMQ转化为求笛卡尔树LCA,就变成O(1)的了。
安利一下自己博客
找时间搞搞吧......
求最近公共祖先(LCA)的各种算法的更多相关文章
- 近期公共祖先(LCA)——离线Tarjan算法+并查集优化
一. 离线Tarjan算法 LCA问题(lowest common ancestors):在一个有根树T中.两个节点和 e&sig=3136f1d5fcf75709d9ac882bd8cfe0 ...
- POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)
Tarjan算法的详细介绍,请戳: http://www.cnblogs.com/chenxiwenruo/p/3529533.html #include <iostream> #incl ...
- 最近公共祖先LCA Tarjan 离线算法
[简介] 解决LCA问题的Tarjan算法利用并查集在一次DFS(深度优先遍历)中完成所有询问.换句话说,要所有询问都读入后才开始计算,所以是一种离线的算法. [原理] 先来看这样一个性质:当两个节点 ...
- POJ1470Closest Common Ancestors 最近公共祖先LCA 的 离线算法 Tarjan
该算法的详细解释请戳: http://www.cnblogs.com/Findxiaoxun/p/3428516.html #include<cstdio> #include<alg ...
- 求LCA最近公共祖先的在线ST算法_C++
ST算法是求最近公共祖先的一种 在线 算法,基于RMQ算法,本代码用双链树存树 预处理的时间复杂度是 O(nlog2n) 查询时间是 O(1) 的 另附上离线算法 Tarjan 的链接: http ...
- 【LCA求最近公共祖先+vector构图】Distance Queries
Distance Queries 时间限制: 1 Sec 内存限制: 128 MB 题目描述 约翰的奶牛们拒绝跑他的马拉松,因为她们悠闲的生活不能承受他选择的长长的赛道.因此他决心找一条更合理的赛道 ...
- 最近公共祖先 LCA 倍增算法
树上倍增求LCA LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 ...
- POJ 1986 Distance Queries (Tarjan算法求最近公共祖先)
题目链接 Description Farmer John's cows refused to run in his marathon since he chose a path much too lo ...
- 用“倍增法”求最近公共祖先(LCA)
1.最近公共祖先:对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.的祖先且x的深度尽可能大. 2.朴素算法:记录下每个节点的父亲,使节点u,v一步一步地向上找 ...
随机推荐
- quartz详解3:quartz数据库集群-锁机制
http://blog.itpub.NET/11627468/viewspace-1764753/ 一.quartz数据库锁 其中,QRTZ_LOCKS就是Quartz集群实现同步机制的行锁表,其表结 ...
- C++ 模板练习1
//特定的模板友元关系 #include "stdafx.h" #include <iostream> using namespace std; template< ...
- python类(2)
#从python开始学习编程 学习笔记 以后看书时注意一下书上表述:好像是类属性attribute,对象特性property,对象方法 1.对于一个类下的全部个体来说,某些属性可能存在个体差异.不是所 ...
- 使用代理IP访问网络
现在很多领域都需要用到代理IP,用到的领域越来越广,如爬虫.投票.抢购等等. 代理IP免费获取地址:http://www.xicidaili.com/(少部分可以用) 我这个案例使用的上面地址里面的免 ...
- java.lang.SecurityException: Permission denied (missing INTERNET permission?)
ndroid app里试图用HttpUrlConnection获取网络连接,忘记在AndroidManifest清单文件里声明需要用到Internet的权限,运行时报此错误. 解决方法 在Androi ...
- Java 接口理解
学习Spring有一段时间了,对java也有了一点了解,最不能理解的就是接口, 即使是写了接口并实现了它,依然无法理解它到底有什么用?看了其他几篇博客,总结了一下自己的理解. 在JAVA编程语言中是一 ...
- frp内网穿透,centos7+frp成功样例
准备工作: 阿里云服务器一台,备案域名一个,本地服务器一台(本人用的虚拟机centos7) frp文件:frp_0.22.0_linux_amd64.tar.gz 链接:https://pan.bai ...
- elasticsearch-填坑
1.分页参数 ,start=(page-1)*size 2.高亮字段结果的处理方式 3.float类型参数从map中取出用double接收 4.结果封装需手动封装 package com.search ...
- 第22章 Makefile基础
一.自动处理头文件的依赖关系 在Makefile中插入如下代码: include $(sources:.c=.d) %.d: %.c set -e; rm -f $@; \ $(CC) -MM $(C ...
- 给锚点a标签添加滑动效果
a标签是前端必用之一,但是a标签点击后马上跳到了href属性值处,有时候要达到滑动效果就要自己添加JavaScript 普通的a标签代码写好之后,在js脚本内加上 $("a").c ...