LCA 近期公共祖先 小结

以poj 1330为例。对LCA的3种经常使用的算法进行介绍,分别为

1. 离线tarjan

2. 基于倍增法的LCA

3. 基于RMQ的LCA



1. 离线tarjan

/*poj 1330 Nearest Common Ancestors
题意:
给出一棵大小为n的树和一个询问(u,v), 问(u,v)的近期公共祖先。
限制:
2 <= n <= 10000
思路:
离线tarjan
*/
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define PB push_back
const int N=10005; int fa[N];
vector<int> tree[N],query[N];
int anc[N]; //ancestor
bool vis[N];
int get_fa(int x){
if(x!=fa[x]) return fa[x]=get_fa(fa[x]);
return x;
} void merge(int x,int y){
int fa_x=get_fa(x);
int fa_y=get_fa(y);
if(fa_x==fa_y) return ;
fa[fa_y]=fa_x;
}
//就是把搜过的合并在一起
void LCA(int rt){
anc[rt]=rt;
for(int i=0;i<tree[rt].size();++i){
int ch=tree[rt][i];
LCA(ch);
merge(rt,ch);
//cout<<rt<<' '<<ch<<' '<<get_fa(ch)<<endl;
anc[get_fa(ch)]=rt;
}
vis[rt]=true;
for(int i=0;i<query[rt].size();++i){
if(vis[query[rt][i]]){
cout<<anc[get_fa(query[rt][i])]<<endl;
return ;
}
}
}
void init(int n){
memset(vis,0,sizeof(vis));
memset(anc,0,sizeof(anc));
for(int i=0;i<=n;++i){
fa[i]=i;
tree[i].clear();
query[i].clear();
}
} int indeg[N];
void gao(int n){
for(int i=1;i<=n;++i){
if(indeg[i]==0){
LCA(i);
break;
}
}
} int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
init(n);
int u,v;
for(int i=0;i<n-1;++i){
scanf("%d%d",&u,&v);
tree[u].PB(v);
++indeg[v];
}
scanf("%d%d",&u,&v);
query[u].PB(v);
query[v].PB(u);
gao(n);
}
return 0;
}

2. 基于倍增法的LCA

/*poj 1330 Nearest Common Ancestors
题意:
给出一棵大小为n的树和一个询问(u,v), 问(u,v)的近期公共祖先。
限制:
2 <= n <= 10000
思路:
基于倍增法的算法,
朴素的算法为:
假设节点w是u和v的公共祖先的话,首先让u。v中较深的一方向上走|depth(u)-depth(v)|步。然后再一步一步向上走。直到同一个节点
基于倍增法的优化:
对于每一个节点预处理出2, 4, 8, ... 2^k步的祖先,然后搞。
*/
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define PB push_back
const int N=100005;
const int LOGN=22; vector<int> tree[N]; int fa[N][LOGN];
int depth[N]; void dfs(int u,int p,int d){
depth[u]=d;
fa[u][0]=p;
for(int i=0;i<tree[u].size();++i){
if(tree[u][i]!=p)
dfs(tree[u][i],u,d+1);
}
} int LCA(int u,int v){
if(depth[u]>depth[v]) swap(u,v);
for(int i=0;i<LOGN;++i){
if(((depth[v]-depth[u]) >> i) & 1)
v=fa[v][i];
}
if(u==v) return u;
for(int i=LOGN-1;i>=0;--i){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0];
} int indeg[N]; void predo(int n){
int root;
for(int i=1;i<=n;++i){
if(!indeg[i]){
root=i;
break;
}
}
dfs(root,-1,0);
for(int j=0;j+1<LOGN;++j){
for(int i=1;i<=n;++i){
if(fa[i][j]<0) fa[i][j+1]=-1;
else fa[i][j+1]=fa[fa[i][j]][j];
}
}
} void init(int n){
memset(indeg,0,sizeof(indeg));
for(int i=0;i<=n;++i)
tree[i].clear();
} int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
init(n);
int u,v;
for(int i=0;i<n-1;++i){
scanf("%d%d",&u,&v);
tree[u].PB(v);
++indeg[v];
}
predo(n);
scanf("%d%d",&u,&v);
printf("%d\n",LCA(u,v));
}
return 0;
}

3. 基于RMQ的LCA

/*poj 1330 Nearest Common Ancestors
题意:
给出一棵大小为n的树和一个询问(u,v), 问(u,v)的近期公共祖先。
限制:
2 <= n <= 10000
思路:
对于涉及有根树的问题。将树转化为从根dfs标号后得到的序列处理的技巧经常十分有效。对于LCA。这个技巧也十分有效。首先。按从根dfs訪问的顺序得到顶点序列vs[i]和相应的深度depth[i],对于每一个顶点v,记其在vs中首次出现的下标为id[v]。
这些都能够dfs一遍搞定。而LCA(u,v)就是訪问u之后到訪问v之前所经过顶点中离根近期的那个。如果id[u] <= id[v]则有:
LCA(u,v) = vs[id[u] <= i <= id[v]中令depth(i)最小的i]
这个能够用rmq求得。 */
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
#define PB push_back
const int N=100005; int dp[N*2][18];
void make_rmq_index(int n,int b[]){ //返回最小值相应的下标
for(int i=0;i<n;i++)
dp[i][0]=i;
for(int j=1;(1<<j)<=n;j++)
for(int i=0;i+(1<<j)-1<n;i++)
dp[i][j]=b[dp[i][j-1]] < b[dp[i+(1<<(j-1))][j-1]]? dp[i][j-1]:dp[i+(1<<(j-1))][j-1];
}
int rmq_index(int s,int v,int b[]){
int k=(int)(log((v-s+1)*1.0)/log(2.0));
return b[dp[s][k]]<b[dp[v-(1<<k)+1][k]]? dp[s][k]:dp[v-(1<<k)+1][k];
} vector<int> tree[N];
int vs[N*2]; //dfs訪问的顺序
int depth[N*2]; //节点的深度
int id[N]; //各个顶点在vs中首次出现的下标 void dfs(int u,int p,int d,int &k){
id[u]=k;
vs[k]=u;
depth[k++]=d;
for(int i=0;i<tree[u].size();++i){
if(tree[u][i]!=p){
dfs(tree[u][i],u,d+1,k);
vs[k]=u;
depth[k++]=d;
}
}
} int indeg[N]; void predo(int n){
int root;
for(int i=1;i<=n;++i){
if(indeg[i]==0){
root=i;
break;
}
}
int k=0;
dfs(root,-1,0,k); /*
for(int i=0;i<k;++i)
cout<<i<<':'<<vs[i]<<' ';
cout<<endl;
for(int i=0;i<k;++i)
cout<<i<<':'<<depth[i]<<' ';
cout<<endl;
for(int i=1;i<=n;++i)
cout<<i<<':'<<id[i]<<' ';
cout<<endl;
*/ make_rmq_index(n*2-1,depth);
} int LCA(int u,int v){
return vs[rmq_index(min(id[u],id[v]), max(id[u],id[v])+1, depth)];
} void init(int n){
memset(indeg,0,sizeof(indeg));
for(int i=1;i<=n;++i)
tree[i].clear();
} int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
init(n);
int u,v;
for(int i=0;i<n-1;++i){
scanf("%d%d",&u,&v);
tree[u].PB(v);
++indeg[v];
}
predo(n);
scanf("%d%d",&u,&v);
printf("%d\n",LCA(u,v));
}
return 0;
}
/*
input:
1
5
2 3
3 4
3 1
1 5
3 5 output:
0:2 1:3 2:4 3:3 4:1 5:5 6:1 7:3 8:2
0:0 1:1 2:2 3:1 4:2 5:3 6:2 7:1 8:0
1:4 2:0 3:1 4:2 5:5
3
*/

LCA 近期公共祖先 小结的更多相关文章

  1. LCA近期公共祖先

    LCA近期公共祖先 该分析转之:http://kmplayer.iteye.com/blog/604518 1,并查集+dfs 对整个树进行深度优先遍历.并在遍历的过程中不断地把一些眼下可能查询到的而 ...

  2. 连通分量模板:tarjan: 求割点 &amp;&amp; 桥 &amp;&amp; 缩点 &amp;&amp; 强连通分量 &amp;&amp; 双连通分量 &amp;&amp; LCA(近期公共祖先)

    PS:摘自一不知名的来自大神. 1.割点:若删掉某点后.原连通图分裂为多个子图.则称该点为割点. 2.割点集合:在一个无向连通图中,假设有一个顶点集合,删除这个顶点集合,以及这个集合中全部顶点相关联的 ...

  3. POJ 1470 Closest Common Ancestors【近期公共祖先LCA】

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u013912596/article/details/35311489 题目链接:http://poj ...

  4. 近期公共祖先(LCA)——离线Tarjan算法+并查集优化

    一. 离线Tarjan算法 LCA问题(lowest common ancestors):在一个有根树T中.两个节点和 e&sig=3136f1d5fcf75709d9ac882bd8cfe0 ...

  5. HDU 2586 How far away ?(LCA模板 近期公共祖先啊)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2586 Problem Description There are n houses in the vi ...

  6. POJ1330Nearest Common Ancestors——近期公共祖先(离线Tarjan)

    http://poj.org/problem? id=1330 给一个有根树,一个查询节点(u,v)的近期公共祖先 836K 16MS #include<iostream> #includ ...

  7. LintCode 近期公共祖先

    中等 近期公共祖先 查看执行结果 34% 通过 给定一棵二叉树,找到两个节点的近期公共父节点(LCA). 近期公共祖先是两个节点的公共的祖先节点且具有最大深度. 您在真实的面试中是否遇到过这个题? Y ...

  8. lca 最近公共祖先

    http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ...

  9. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)

    Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...

随机推荐

  1. jquery事件重复绑定的几种解决方法

    防止事件重复绑定共有4种方法: bind().unbind()方法 live().die()方法 off().on()方法 one()方法 一.bind().unbind()方法 bind();绑定事 ...

  2. Hdu-6253 2017CCPC-Final K.Knightmare 规律

    题面 题意:给你一个无限大的棋盘,一个象棋中的马,问你这个马,飞n步后,可能的位置有多少种? 题解:看到题,就想先打表试试,于是先写个暴力(枚举每个位置,是马就飞周围8个格子,注意不要在同个循环里把格 ...

  3. .net core发布到IIS后502.5错误

    net core 在win7系统发布后,出现在502.5错误. 打开“开始”菜单,搜索“事件查看器”,然后选择“事件查看器”应用. 在“事件查看器”中,打开“Windows 日志”节点. 选择“应用程 ...

  4. guice基本学习,guice的学习资料(十)

    这个是我前面几篇的参考. guice的学习资料下载:http://pan.baidu.com/s/1bDEPem 路途遥远,但是人确在走.不忘初心,方得始终.

  5. window下安装svn

    下载 http://subversion.apache.org/ 注意:上边的黑窗口不要关闭! 如何校验svn服务有运行

  6. Kettle通用数据贴源作业设计

    本设计基于以下需求提出 1. 快速接入数据源表(贴源/落地) 2. 无须给单独表开发转换/作业 3. 动态生成数据源连接, 表字段等信息(预先保存在数据仓库中) 本设计所需条件 1. 数据源为关系型数 ...

  7. SPL类

    用途:对类,方法,属性,参数的提取生成文档:自动加载插件 实列化类同于new:$ref = new ReflectionClass($classname);$class = $ref->newI ...

  8. 03--软件包管理工具 apt

    APT       APT(the Advanced Packaging Tool)是Ubuntu 软件包管理系统的高级界面,由几个名字以“apt-”打头的程序组成.apt-get.apt-cache ...

  9. js 把json字符串转为json对象

       <input type="hidden" name="data" id="data" value='[{"name&q ...

  10. URLLib库使用

    Date: 2019-06-19 Author: Sun urllib ​ 在Python 3以后的版本中,urllib2这个模块已经不单独存在(也就是说当你import urllib2时,系统提示你 ...