求最近公共祖先(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一步一步地向上找 ...
随机推荐
- oo第四单元及课程总结
一.第四单元作业总结 第四单元有两次作业,第十三次作业是实现一个UML类图解析器,可以通过输入一些查询指令来查询一些类图的信息.程序的主干部分已经提供,我们的任务就是实现给出的接口,过程并不繁琐.第十 ...
- MySQL的异常问题
异常问题
- POJ 2251:Dungeon Master
Dungeon Master Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 20687 Accepted: 8004 D ...
- eclipse中tomcat添加或移除web项目出错,显示无资源能被添加或移除
错误截图 之前一直都能正常使用,今天莫名其妙出现这个错误 解决办法 https://blog.csdn.net/u012956987/article/details/79134474 右击项目,在属性 ...
- 动态加载JS文件方法总结
1.JQuery方法 $.getScript("./test.js"); //加载js文件 $.getScript("./test.js",function() ...
- TCP三次握手和四次挥手相关
客户端A 服务端BSYN (建立连接位标识 1为建立联机) ACK (确认位标识 1为确认) seq (一个随机顺序码) ack(一个确认号码,通常为seq+1) 三次握手:1.A 发起建立 连接 的 ...
- pip常见使用方法
pip可以理解类似yum管理rpm包的管理python包工具 pip参数解释 pip --help Usage: pip <command> [options] Commands: ins ...
- Python—在Django中使用Celery
一.Django中的请求 Django Web中从一个http请求发起,到获得响应返回html页面的流程大致如下: http请求发起 经过中间件 http handling(request解析) ur ...
- CentOS7下MySQL8的二进制基本安装配置
前言 基于本地Centos7.6虚拟机Mysql8的配置(亲测有效) 一.安装前的准备 1.到官网下载mysql-8.0.16-linux-glibc2.12-x86_64.tar.xz 2.通过Xs ...
- @ConfigurationProperties的几个使用细节
1.只有当前组件是容器中的组件,才能使用容器提供的@ConfigurationPropeities功能 2.使用@ConfigurationProperties,被注入字段必须要有set .get 方 ...