求最近公共祖先(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一步一步地向上找 ...
随机推荐
- PAT 2019 春
算是第二次做这套题吧,感觉从上次考试到现在自己有了挺大提高,提前30min做完了. 7-1 Sexy Primes 读懂题意就行. #include <cstdio> #include & ...
- [CISCN 2019 初赛]Love Math
0x00 知识点 PHP函数: scandir() 函数:返回指定目录中的文件和目录的数组. base_convert() 函数:在任意进制之间转换数字. dechex() 函数:把十进制转换为十六进 ...
- arp攻击 (可查看同一局域网他人手机照片)
国家法律一定要遵守,知识要用在对的地方. 本贴只为了和大家交流学习,请勿用在其他地方,损害任何人的利益. 今天我,来说一下arp攻击的原理和教程 原理什么的还是自行百度好,因为专业的说明是严谨而又经得 ...
- dxSkinController1 皮肤使用
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System ...
- 专为前端开发者准备的15款优秀的Sublime Text插件
Sublime Text 已成为了目前最流行的代码编辑器之一.它的反应速度.简单易用性以及丰富的插件生态,让众多前端开发者们为之倾倒. 为了帮助开发者们更便捷地使用 Sublime Text ,我们决 ...
- CodeForces 366C 动态规划 转化背包思想
这道题目昨晚比赛没做出来,昨晚隐约觉得就是个动态规划,但是没想到怎么DP,今天想了一下,突然有个点子,即局部最优子结构为 1-j,j<i,遍历i,每次从所有的1到j当中的最优解里面与当前商品进行 ...
- Bugku杂项(1—28)
1.签到题 只要关注公众号就可以得到 flag---开胃菜 2.这是一张单纯的图片 用Winhex打开,会发现最下面有一行编码: key{you are right} 是一串HTML编码,解密下就行了 ...
- vue图片查看器
vue 安装图片查看器插件安装cnpm install v-viewer引用 import 'viewerjs/dist/viewer.css' import Viewer from 'v-viewe ...
- h5-提升移动端的响应区域
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- python版本,执行
01. 第一个 HelloPython 程序 1.1 Python 源程序的基本概念 Python 源程序就是一个特殊格式的文本文件,可以使用任意文本编辑软件做 Python 的开发 Python 程 ...