「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 ... 
