「LuoguP3379」 【模板】最近公共祖先(LCA)
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入输出格式
输入格式:
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式:
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
样例说明:
该树结构如下:

第一次询问:2、4的最近公共祖先,故为4。
第二次询问:3、2的最近公共祖先,故为4。
第三次询问:3、5的最近公共祖先,故为1。
第四次询问:1、2的最近公共祖先,故为4。
第五次询问:4、5的最近公共祖先,故为4。
故输出依次为4、4、1、4、4。
题解
其实是放一下代码
众所周知,LCA有几种常见的做法
- 暴力跳
- 先把较深的往上跳,跳到同一深度
- 然后一起跳
- 单次复杂度$O(n)$分分钟带你上天
- 倍增
- 在跳的时候优化一下,不一格一格的跳,而是拆分成二进制跳
- 是对暴力跳选手思维难度上最友好的升级方式
- 需要预处理出每个点往上$2^i$步的祖先,
- 时间复杂度$O(nlogn+mlogn)$,空间复杂度$O(nlogn)$
/*
qwerta
P3379 【模板】最近公共祖先(LCA)
Accepted
100
代码 C++,1.37KB
提交时间 2018-03-13 18:33:35
耗时/内存
1672ms, 51789KB
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct emm{
int f,e;
}a[];
int h[];
int d[];
int p[][];
void dfs(int no,int fa)
{
d[no]=d[fa]+;
//cout<<no<<" "<<d[no]<<" "<<fa<<endl;
p[no][]=fa;
int w;
for(w=;w<;w++)
p[no][w]=p[p[no][w-]][w-];
for(w=h[no];w;w=a[w].f)
{
if(a[w].e!=fa)
dfs(a[w].e,no);
}
return;
}
int main()
{
int c=,x,y,n,m,s,i,j;
scanf("%d%d%d",&n,&m,&s);
for(i=;i<n;++i)
{
scanf("%d%d",&x,&y);
++c;
a[c].f=h[x];
h[x]=c;
a[c].e=y;
++c;
a[c].f=h[y];
h[y]=c;
a[c].e=x;
d[i]=;
}
d[n]=;
dfs(s,);
for(i=;i<=m;++i)
{
scanf("%d%d",&x,&y);
if(d[x]<d[y])swap(x,y);
for(j=;j>=;--j)
{
if((d[x]-d[y])>=(<<j))
{
x=p[x][j];
//cout<<x<<" ";
}
}
if(x==y)printf("%d\n",x);
else{
for(j=;j>=;--j)
{
if(p[x][j]!=p[y][j])
{
x=p[x][j];
y=p[y][j];
}
}
printf("%d\n",p[x][]);}
}
/*
for(i=1;i<=n;i++)
{
cout<<i<<" ";
for(j=0;j<=19;j++)
cout<<p[i][j]<<" ";
cout<<endl;
}*/
return ;
} 倍增求LCA倍增求LCA
- ST表
- 原理:dfs序在这两点之间 的点中,深度最小的点为lca
- 所以记录dfs序和深度,在区间上找最小值,转化为RMQ问题。
- 需要预处理dfs序和ST表。
- 时间复杂度$O(n+nlogn+mlogn)$,空间复杂度$O(3*n+nlogn)$
- 理论上比倍增慢一丢丢。
/*
qwerta
P3379 【模板】最近公共祖先(LCA)
Accepted
100
代码 C++,2.28KB
提交时间 2018-06-24 11:36:03
耗时/内存
1992ms, 98929KB
*/
#include<cstdio>
using namespace std;
int n,m,s;
struct emm{
int e,f;
}a[];
int h[];
int c;
inline int read()
{
int x=;
char c=getchar();
while(c<''||c>'') c=getchar();
while(c>=''&&c<='') x=(x<<)+(x<<)+c-'',c=getchar();
return x;
}
inline void write(int x)
{
if(x>) write(x/);
putchar(x%+'');
return;
}
inline int min(int qwq,int qaq){return qwq<qaq?qwq:qaq;}
inline void swap(int &qq,int &ww){int ee=qq;qq=ww;ww=ee;return;}
inline void con(int q,int w)
{
a[++c].f=h[q];
h[q]=c;
a[c].e=w;
return;
}
inline void scan()
{
n=read(),m=read(),s=read();
//scanf("%d%d%d",&n,&m,&s);
int x,y;
for(register int i=;i<n;++i)
{
x=read(),y=read();
//scanf("%d%d",&x,&y);
con(x,y);
con(y,x);
}
return;
}
int fir[];
int pl[];
int d[];
int f[][];
int dd;
void dfs(int x)
{
d[x]=++dd;
pl[++c]=x;
//printf("%d %d %d %d\n",c,x,d[c],pl[c]);
if(!fir[x])fir[x]=c;
for(register int i=h[x];i;i=a[i].f)
{
int u=a[i].e;
if(!fir[u])
{
dfs(u);
pl[++c]=x;
//printf("%d %d %d %d\n",c,x,d[c],pl[c]);
}
}
--dd;
return;
}
inline void rmq()
{
for(register int i=;i<=c;++i)
f[i][]=pl[i];
for(register int j=;(<<j)<=c;++j)
for(register int i=;i+(<<j)-<=c;++i)
{
if(d[f[i][j-]]<d[f[i+(<<(j-))][j-]])
f[i][j]=f[i][j-];
else f[i][j]=f[i+(<<(j-))][j-];
}
return;
}
inline void predo()
{
c=;
dfs(s);
rmq();
return;
}
inline void find(int x,int y)
{
int l=fir[x],r=fir[y];
if(l>r)swap(l,r);
int p;
//cout<<l<<" "<<r<<" ";
for(register int j=;j>=;--j)
if(l+(<<j)-<=r)
{
//cout<<f[l][j]<<" "<<f[r-(1<<j)+1][j]<<endl;
//cout<<l<<" "<<r<<" "<<j<<endl;
p=d[f[l][j]]<d[f[r-(<<j)+][j]]
?f[l][j]:f[r-(<<j)+][j];
//p=min(f[l][j],f[r-(1<<j)+1][j]);
write(p);putchar('\n');
return;
}
//cout<<"a"<<endl;
return;
}
inline void run()
{
for(register int i=;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
find(x,y);
}
return;
}
int main()
{
scan();
predo();
run();
return ;
}ST表求LCA
- tarjan
- 和求强连通分量的tarjan不是一个东西。
- 不太了解,应该是最小众的做法了叭,据说有常数上的优势?
- 其实以前是听懂过的,但是仗着自己已经会两种做法了就飘了没写
- 树链剖分
- 乍一听挺二了吧唧的,以前觉得像各种A+B的题解一样装哔
- 但是自从会了树剖之后我的倍增和ST表就忘光了a!(不知道该开心还是难过
- 对于会树剖的选手而言,又好写又不用过脑子还快。
- 需要预处理遍历两遍,和做一个gettop的操作,正式跑的过程应该比倍增和ST快。
- 反正我写出来快了不少。
/*
qwerta
P3379 【模板】最近公共祖先(LCA)
Accepted
100
代码 C++,1.48KB
提交时间 2018-10-09 19:16:23
耗时/内存
1043ms, 20392KB
*/
#include<cstdio>
#include<iostream>
using namespace std;
#define R register
const int MAXN=;
struct emm{
int e,f;
}a[*MAXN];
int h[MAXN];
int tot=;
void con(int x,int y)
{
a[++tot].f=h[x];
h[x]=tot;
a[tot].e=y;
a[++tot].f=h[y];
h[y]=tot;
a[tot].e=x;
return;
}
int fa[MAXN],d[MAXN],siz[MAXN],z[MAXN],top[MAXN];
void dfs(int x)
{
siz[x]=;
top[x]=x;
int mac=,macc=-;
for(R int i=h[x];i;i=a[i].f)
if(!d[a[i].e])
{
d[a[i].e]=d[x]+;
fa[a[i].e]=x;
dfs(a[i].e);
siz[x]+=siz[a[i].e];
if(siz[a[i].e]>macc){mac=a[i].e;macc=siz[a[i].e];}
}
z[x]=mac;
top[mac]=x;
return;
}
int fitop(int x)
{
if(top[x]==x)return x;
return top[x]=fitop(top[x]);
}
inline int read()
{
char ch=getchar();
int x=;
while(!isdigit(ch))ch=getchar();
while(isdigit(ch)){x=x*+ch-'';ch=getchar();}
return x;
}
void write(int x)
{
if(x>)write(x/);
putchar(x%+'');
return;
}
int main()
{
//freopen("a.in","r",stdin);
int n=read(),m=read(),s=read();
for(R int i=;i<n;++i)
{
int x=read(),y=read();
con(x,y);
}
d[s]=;
dfs(s);
for(R int i=;i<=n;++i)
top[i]=fitop(i);
for(R int c=;c<=m;++c)
{
int u=read(),v=read();
while(top[u]!=top[v])
{
if(d[top[u]]>d[top[v]])u=fa[top[u]];
else v=fa[top[v]];
}
write(d[u]<d[v]?u:v);
putchar('\n');
}
return ;
}树剖求LCA
也许还有别的做法叭,太弱了不了解。
吸氧?卡常?不存在的我跟你说,
吸氧是不可能的,这辈子都不可能的,老子复杂度这么优秀吸什么氧?!
——我屮艸芔茻加个register吸个氧就减到三分之二?!这么香?!!
总结
在倍增和ST之间推荐倍增,思维难度低,效率还蛮不错。
但是也见过考试考RMQ问题的...我校倍增选手当场哭出声2333
然后会了树剖还写这些个毛,多难想啊2333
其实我只是放一下代码的qwq
UPD
我校选手看过来!
这里是我下午当场出锅的代码(qaq
/*
qwerta
P3379 【模板】最近公共祖先(LCA)
Accepted
100
代码 C++,1.18KB
提交时间 2018-10-14 16:54:45
耗时/内存
2037ms, 53444KB
*/
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define R register
struct emm{
int to,nxt;
}a[];
int h[];//邻接链表存图
int cnt=;
inline void con(int x,int y)//连边
{
a[++cnt].nxt=h[x];
h[x]=cnt;
a[cnt].to=y;
a[++cnt].nxt=h[y];
h[y]=cnt;
a[cnt].to=x;
return;
}
int fa[],d[];
void dfs(int x)//dfs建树
{
for(R int i=h[x];i;i=a[i].nxt)
if(!d[a[i].to])
{
d[a[i].to]=d[x]+;
fa[a[i].to]=x;
dfs(a[i].to);
}
return;
}
int la[][];//向上2^j步的祖先
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(R int i=;i<n;++i)
{
int x,y;
scanf("%d%d",&x,&y);//读边
con(x,y);
}
d[s]=;
fa[s]=s;
dfs(s);
for(R int i=;i<=n;++i)
la[i][]=fa[i];
for(R int j=;j<=;++j)
for(R int i=;i<=n;++i)
la[i][j]=la[la[i][j-]][j-];
//cout<<endl;
for(R int i=;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
if(d[x]<d[y])swap(x,y);
//
for(R int j=;j>=;--j)
if(d[x]-(<<j)>=d[y])
x=la[x][j];
//same depth
if(x==y){printf("%d\n",y);continue;}
for(R int j=;j>=;--j)
if(la[x][j]!=la[y][j])
x=la[x][j],y=la[y][j];
//cout<<x<<" "<<y<<" "<<endl;
printf("%d\n",fa[x]);
}
return ;
}
都追到这里了要给老学姐点个关注吖!QAQ
「LuoguP3379」 【模板】最近公共祖先(LCA)的更多相关文章
- [模板] 最近公共祖先/lca
简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...
- Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)
Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...
- POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)
POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- 【lhyaaa】最近公共祖先LCA——倍增!!!
高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...
- 最近公共祖先(LCA)模板
以下转自:https://www.cnblogs.com/JVxie/p/4854719.html 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖 ...
- HDU 2586 How far away ?(LCA模板 近期公共祖先啊)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2586 Problem Description There are n houses in the vi ...
- luogu3379 【模板】最近公共祖先(LCA) 倍增法
题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 整体步骤:1.使两个点深度相同:2.使两个点相同. 这两个步骤都可用倍增法进行优化.定义每个节点的Elder[i]为该节点的2^k( ...
- 最近公共祖先lca模板
void dfs(int x,int root){//预处理fa和dep数组 fa[x][0]=root; dep[x]=dep[root]+1; for(int i=1;(1<<i)&l ...
随机推荐
- 如何模拟alert/confirm/prompt实现阻断程序运行
场景:在执行js的时候,我们希望运行到某处,进行用户交互,根据交互的内容,运行下面的程序:下面的js程序需要用的和用户交互的内容,所以,和用户交互时,后面的程序必须停止运行 方案: 1. 原生的ale ...
- 【经验之谈】适合学习的IT教程站点列表
① CSDN http://www.csdn.net/ ② gitHub https://github.com/ ③ 极客学院 http://www.jikexueyuan.com/ ...
- 百科知识 .tar.xz文件如何打开
7-ZIP可以打开,右击提取到当前目录即可 发现这个压缩比例还是相当不一般的,都快十倍了.
- 赵雅智_Fragment
当我们须要动态的多界面切换的时候,就须要将UI元素和Activity融合成一个模块. 在2.3中我们一般通过各种Activity中进行跳转来实现多界面的跳转和单个界面动态改变.在4.0或以上系统中就能 ...
- Odoo event
使用流程 建立活动 发布到网站 在线销售 订单确认,付款确认 注册.出席 建立活动 设置门票 确认并发布到网站 进入编辑模式,即可在线编辑活动 ...
- python 大小写转换方法(全)
http://blog.csdn.net/liuxincumt/article/details/7945337 python大小写转换(全)
- C#编译器选项(目标平台)
用vs编译C#项目的设置中,“属性-生成-目标平台”有anycpu,x86,x64等选项. anycpu(默认值)将编译程序集为使其在任意平台上都可以运行. 在任何可能的时候,应用程序作为 64 位进 ...
- qemu-kvm编译错误
今天编译了下qemu-kvm,死活编不过,错误信息如下: CC block/qcow2-snapshot.oIn file included from ./qemu-common.h:6:0, ...
- PE添加Style
1. <style id="NumberStyle"> <setting> <param name="option"> ...
- 在html文件引入其它html文件的几种方法
1.IFrame引入,看看下面的代码 <IFRAME NAME="content_frame" width=100% height=600 marginwidth=0 mar ...