求最近公共祖先(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一步一步地向上找 ...
随机推荐
- SpringMVC:提交参数名与接收参数名问题
1.提交的域名称和处理方法的参数名一致 提交数据 : http://localhost:8080/hello?name=111 处理方法 : @RequestMapping("/hello& ...
- h5-transform-3d
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- textField 基本属性
_textField.frame = CGRectMake(0, 0, 200, 50); _textField.delegate = self; _textField.text = str; [_t ...
- Unity获取游戏对象详解
我觉得Unity里面的Transform 和 GameObject就像两个双胞胎兄弟一样,这俩哥们很要好,我能直接找到你,你也能直接找到我.我看很多人喜欢在类里面去保存GameObject对象.解决G ...
- 吴裕雄--天生自然MySQL学习笔记:MySQL 正则表达式
下表中的正则模式可应用于 REGEXP 操作符中. 实例 查找name字段中以'st'为开头的所有数据: mysql> SELECT name FROM person_tbl WHERE nam ...
- Physicoochemical|CG content|
NCBI存在的问题: 数据用户的增长 软件开发受限 数据分析缺乏 有些传统束缚,仅用底层语言书写 Pangenome Open gene是随菌株数量增大而增大的gene,Closed gene是随菌株 ...
- Apache添加ssl支持
安装证书文件说明:1. 证书文件xxx.pem,包含两段内容,请不要删除任何一段内容.2. 如果是证书系统创建的CSR,还包含:证书私钥文件xxx.key.证书公钥文件public.pem.证书链文件 ...
- SeetaFaceEngine系列2:Face Alignment编译和使用
前面一篇写了编译人脸检测部分,现在就介绍下人脸配准部分,SeetaFace的Face Alignment通过人脸的五个关键点来配准人脸,也就是双眼.鼻尖.两个嘴角. 这部分的编译也和上一篇一样,步骤如 ...
- 3. 现代 javascript 数组专题 和 对象专题
数组专题 展开运算符 使用...符号, 可以将数组"展开". 数组展开的妙用 ... eg: // 代替apply const foo = [1, 2, 3] const bar ...
- HDU-3038 How Many Answers Are Wrong(带权并查集区间合并)
http://acm.hdu.edu.cn/showproblem.php?pid=3038 大致题意: 有一个区间[0,n],然后会给出你m个区间和,每次给出a,b,v,表示区间[a,b]的区间和为 ...