最近公共祖先(lca)
囧啊囧。
lca的求法太多了
倍增,tarjan,st,lct,hld....
后边三个我就不写了,其中st我没写过,估计用不上,在线用倍增,离线用tarjan就行了。
嗯。
第一种,倍增(O(nlogn)~O(logn),在线):
倍增的思想用在树上,即可以求出lca。
我们维护二维数组,f[i][j],表示i号点的第2^j号祖先,显然2^0=1也就是f[i][0]就是他的父亲
我们需要用dfs维护一个深度数组(求lca需要用)
还需要倍增求出所有的f[i][j],学过st的都应该知道,在这里f[i][j]=f[ f[i][j-1] ][j]
然后是我们的求lca了,很简单,首先要将这两个点u和v调到同一深度,这样以后操作都是同深度的。
怎么调深度呢?很简单,将他们的深度相减,我们设为dep,那么这个dep的就对应了深一点的那个点需要上升的高度,恩,应该马上能想到,直接用二进制表示深度然后一直爬上去就行了,这就是倍增的思想,log级别
同一深度时,我们要同时上升啦~我们继续用倍增思想,依次上升2^k的高度。什么时候上升呢?当然是f[u][k]!=f[v][k]的时候,因为这说明他们的祖先还不同,他们位于2棵子树,所以要上升。并且顺序要从大到小!否则求不到最小的祖先,很容易理解的。
代码很简单,12行
#include <iostream>
#include <cstdio>
using namespace std;
#define dbg(x) cout << #x << " = " << x << endl
#define read(x) x=getint()
#define rdm(u) for(int i=ihead[u]; i; i=e[i].next) const int N=10000, M=15;
inline const int getint() { char c=getchar(); int k=1, ret=0; for(; c<'0'||c>'9'; c=getchar()) if(c=='-') k=-1; for(; c>='0'&&c<='9'; c=getchar()) ret=ret*10+c-'0'; return k*ret; }
struct ed { int to, next; } e[N<<1];
int cnt, ihead[N], n, m, dep[N], fa[N][M];
bool vis[N];
inline void add(const int &u, const int &v) {
e[++cnt].next=ihead[u]; ihead[u]=cnt; e[cnt].to=v;
e[++cnt].next=ihead[v]; ihead[v]=cnt; e[cnt].to=u;
}
void dfs(const int &u, const int &d) {
vis[u]=1; dep[u]=d;
rdm(u) if(!vis[e[i].to]) { dfs(e[i].to, d+1); fa[e[i].to][0]=u; }
}
inline void bz() { for(int j=1; j<M; ++j) for(int i=1; i<=n; ++i) fa[i][j]=fa[fa[i][j-1]][j-1]; }
inline int lca(int u, int v) {
if(dep[u]<dep[v]) swap(u, v);
int d=dep[u]-dep[v];
for(int i=M-1; i>=0; --i) if((1<<i)&d) u=fa[u][i];
if(u==v) return u;
for(int i=M-1; i>=0; --i) if(fa[u][i]!=fa[v][i]) u=fa[u][i], v=fa[v][i];
return fa[u][0];
}
int main() {
read(n); read(m);
for(int i=1; i<n; ++i) add(getint(), getint());
dfs(1, 1); bz();
while(m--) printf("%d\n", lca(getint(), getint()));
return 0;
}
第二种,tarjan(O(n+并查集)~O(1) ,离线):
速度略优于第一种。
tarjan求lca也很好理解的,我们假设现在的点为x
那么它子树作为一个已经被访问完的集合,并且在这些集合内的lca已经全部求出。
那么我们只要将这些子树和他子集合并就行了。
在这个集合求lca的方法很简单,用并查集即可。
代码也很短,也大概12行吧
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
#define dbg(x) cout << #x << " = " << x << endl
#define read(x) x=getint()
#define rdm(u) for(int i=ihead[u]; i; i=e[i].next) const int N=10005, M=10005;
inline const int getint() { char c=getchar(); int k=1, ret=0; for(; c<'0'||c>'9'; c=getchar()) if(c=='-') k=-1; for(; c>='0'&&c<='9'; c=getchar()) ret=ret*10+c-'0'; return k*ret; }
struct ed { int to, next; } e[N<<1];
int cnt, ihead[N], n, m, lca[M], fa[N], p[N];
bool vis[N];
vector<pair<int, int> > q[N];
inline void add(const int &u, const int &v) {
e[++cnt].next=ihead[u]; ihead[u]=cnt; e[cnt].to=v;
e[++cnt].next=ihead[v]; ihead[v]=cnt; e[cnt].to=u;
}
int ifind(const int &x) { return x==p[x]?x:p[x]=ifind(p[x]); }
void tarjan(int u) {
p[u]=u;
rdm(u) if(e[i].to!=fa[u]) {
fa[e[i].to]=u; tarjan(e[i].to); p[e[i].to]=u;
}
vis[u]=1;
int t=q[u].size();
for(int i=0; i<t; ++i) if(vis[q[u][i].first]) lca[q[u][i].second]=ifind(q[u][i].first);
}
int main() {
read(n); read(m);
for(int i=1; i<n; ++i) add(getint(), getint());
int u, v;
for(int i=1; i<=m; ++i) {
read(u); read(v);
q[v].push_back(pair<int, int> (u, i));
q[u].push_back(pair<int, int> (v, i));
}
tarjan(1);
for(int i=1; i<=m; ++i) printf("%d\n", lca[i]);
return 0;
}
最近公共祖先(lca)的更多相关文章
- 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 ...
- [模板] 最近公共祖先/lca
简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...
- 【lhyaaa】最近公共祖先LCA——倍增!!!
高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...
- POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)
Tarjan算法的详细介绍,请戳: http://www.cnblogs.com/chenxiwenruo/p/3529533.html #include <iostream> #incl ...
- 【Leetcode】查找二叉树中任意结点的最近公共祖先(LCA问题)
寻找最近公共祖先,示例如下: 1 / \ 2 3 / \ / \ 4 5 6 7 / \ ...
- 最近公共祖先LCA(Tarjan算法)的思考和算法实现
LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...
- 查找最近公共祖先(LCA)
一.问题 求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树).最近公共祖先简称LCA(Lowest Common Ancestor).例如,如下图一棵普通的二叉树. 结点3和结点4的最近公共 ...
- 最近公共祖先(LCA)的三种求解方法
转载来自:https://blog.andrewei.info/2015/10/08/e6-9c-80-e8-bf-91-e5-85-ac-e5-85-b1-e7-a5-96-e5-85-88lca- ...
随机推荐
- Android自动登录与记住密码
// 获取实例对象 sp = this.getSharedPreferences("userInfo", Context.MODE_WORLD_READABLE); rem_pw ...
- 新浪网易淘宝等IP地区信息查询开放API接口调用方法
通过IP地址获取对应的地区信息通常有两种方法:1)自己写程序,解析IP对应的地区信息,需要数据库.2)根据第三方提供的API查询获取地区信息. 第一种方法,参见文本<通过纯真IP数据库获取IP地 ...
- 使用git如何批量对文件进行rm操作
git add -A 它会把我们未通过 git rm 删除的文件全部stage 转自: http://segmentfault.com/q/1010000000095373
- RabbitMQ消息队列:ACK机制
每个Consumer可能需要一段时间才能处理完收到的数据.如果在这个过程中,Consumer出错了,异常退出了,而数据还没有处理完成,那么 非常不幸,这段数据就丢失了. 因为我们采用no-ack的方式 ...
- iOS 利用self.navigationItem.backBarButtonItem修改后退按钮文字
@property(nonatomic,retain) UIBarButtonItem *backBarButtonItem; // Bar button item to use for the ba ...
- Windows metro app wcf 地址可配置
在Windows metro app中调用wcf服务可以通过添加 “服务引用”来实现.一旦项目发布则不可修改.这个和桌面开发不一样. 现在我们通过读取文本的方式来读取wcf地址. 1.添加所需引用的w ...
- codeforces B. Xenia and Ringroad 解题报告
题目链接:http://codeforces.com/problemset/problem/339/B 题目理解不难,这句是解题的关键 In order to complete the i-th ta ...
- MySQL主备停机步骤与注意事项
双十一马上到了,一堆的事情,今天登录mysql数据库服务器的时候突然发现服务器时间戳不对,比北京时间快了几分钟,我的天...随后检查了其他的几台数据库服务器发现同样都存在不同的偏差,最小的比北京时间快 ...
- css局部概念的理解:
1.DIV-Padding理解:一直以来对div中的padding属性,一直不理解,使用最多的也就是margin,padding是div的内空间的相对距离,margin是div的外部相对位置,如果用一 ...
- ytu 2011: C语言实验——找中间数(水题)
2011: C语言实验——找中间数 Time Limit: 1 Sec Memory Limit: 64 MBSubmit: 212 Solved: 122[Submit][Status][Web ...