【讲解+模板】最近公共祖先(LCA)(倍增)
来源:互联网 发布:广告机软件下载 编辑:程序博客网 时间:2024/05/16 09:40
【模板】最近公共祖先(LCA)(倍增)
阅读须知:我认为读者已经掌握(或了解)了:
- 倍增思想
- 树(图)的基本概念及简单实现
- 存图与建图
- dfs
嗯,我们来看看最近公共祖先(LCA)的一种实现方式——倍增。
最近公共祖先
话说什么是最近公共祖先呢?emmm……大家如果知道树的话,应该就知道父亲节点与儿子节点了吧,那么祖先就是父亲的父亲的父亲的……;总之在同一条树链上,若x的深度小于y的深度,则称x是y的祖先。公共祖先,顾名思义就是两个点共同拥有的祖先;但是若x是y的祖先,则他们的共同祖先是x(很奇怪是不是)。最近公共祖先可以依字面意思理解,即“最近的”公共祖先。下面附图说明~
【图1】
如上图4和7的公共祖先有5、10,最近公共祖先为5;
3与20的公共祖先和最近公共祖先都是10;
2和3的公共祖先是3、5、10,最近公共祖先是3。
怎么样,对最近公共祖先是不是有些了解了。
下面我们再来看两张图~
【图2】
【图3】
请读者们仔细比对上面两张图,会发现,选择不同的根节点会使得两点之间有着不同的最近公共祖先。
倍增
关于实现LCA,想必大家都有暴力的思路,不断询问x与y的父亲节点,直到发现他们询问到相同的父亲节点位置,这当然会超时!所以,我们跳着查找【滑稽】,这就是倍增的思想。
我们使用一个数组lst[i][j]表示从i这个点向上跳2^j次所对应的点,即节点i的第2^j个父亲节点的编号。比如说上面第二个图中lst[5][0]=2,lst[5][1]=1。
请读者们细细体会下面的转化【很重要】(可以画一颗树试一试):
lst[son][i + 1] = lst[ lst[son][i] ][i]
倍增LCA的实现
1.建图(建议使用链式前向星)
2.预处理,更新lst数组
3.查询
下面分条作答
1.建图
链式前向星存图【不了解的话建议百度】
//建树一般不用存边权,建树一般要存双向边struct EDGE{ int to; //到达的点 int next; //上一条边 int w; //边权}edge[2 * max_data]; //双向边int edge_size = 0; //前向星数组模拟指针int head[max_data]; //起点void add(int u, int v, int w){ edge[++edge_size].next = head[u]; edge[edge_size].to = v; edge[edge_size].w = w; head[u] = edge_size;}
2.预处理,更新lst(倍增表)
思想:利用上面说的重要式子更新lst。从根节点出发,遍历每一个点,在遍历时利用父子关系【滑稽】更新lst。
注意:要使根节点深度设为1(否则会使后面的st可能变为-1)。
int vis[max_data]; //防止一个点被遍历多次【重要】int lst[max_data][33]; //倍增表int deep[max_data]; //记录深度int main(){ deep[x] = 1; //根节点深度设为1 dfs(x);}void dfs(int x) //从根节点开始遍历{ vis[x] = 1; for(int i = head[x]; i; i = edge[i].next) //遍历每一个与x相连的点 { int temp = edge[i].to; //下一个点即当前点的儿子节点 if(!vis[temp]) { lst[temp][0] = x; //儿子上跳一位既是父亲 deep[temp] = deep[x] + 1; //儿子比父亲深度加1 for(int j = 0, last = x; lst[last][j]; last = lst[last][j], j++) lst[temp][j + 1] = lst[last][j]; //这里既是用那个重要的式子更新lst dfs(temp); //继续遍历 } }}
也可以不把那个重要式子加进dfs里,也可以dfs后单独再建倍增表(ST表)
void increase__pretreatment(){ for(register int j = 1; j <= 19; ++j)//register为设置寄存器变量,可以比较玄学地加速程序(不建议随便使用) for(register int i = 1; i <= n; ++i) f[i][j] = f[f[i][j - 1]][j - 1]; //倍增预处理}
3.查询
终于到倍增大显身手的时候了,上面我们说到逐层查找会很慢,所以我们跳着查,通过倍增法达到快速上搜的目的。
首先,我们找到一个深度较大的点,利用倍增快速上搜到另一个点相同的深度。
然后,让两个点同时倍增快速上搜,直到搜到他们最近公共祖先的儿子为止。
注意,倍增快速上搜时从较大跨度逐渐向较小跨度搜,这样可以保证搜索到正确答案。
int lca(int x, int y){ if(deep[x] < deep[y]) swap(x, y); //找最深的点 int st; //记录倍增搜索的最大跨度 for(st = 0; (1 << st) <= deep[x]; st++); st--;//查找最大跨度(位运算 x << y 表示x * (2 ^ y)) for(int i = st; i >= 0; i--) //调整较深点到较潜点深度 if(deep[lst[x][i]] >= deep[y]) x = lst[x][i]; //倍增跳跃 if(x == y) return x; for(int i = st; i >= 0; i--) if(lst[x][i] != lst[y][i]) //if会过滤掉倍增跳跃超过所求点深度的点(当然等于也被无情地筛去了),也会在没有跳到lca前实现不断跳跃,最后保证找到lca的儿子节点(lca被筛除) x = lst[x][i], y = lst[y][i]; //跳吧!!! return lst[x][0]; //最后找到的是儿子,所以返回父亲}
完整实战模板
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式:
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式:
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。 时空限制:1000ms,128M
#include<iostream>#include<cmath>#include<cstring>#include<cstdio>#include<cstdlib>#include<map>#include<vector>#include<algorithm>#include<stack>#include<queue>#define by Mashiro_ylb#define Time 2017\10\30using namespace std;const int max_data = 500007;struct EDGE{ int to; int next;}edge[2 * max_data];int edge_size = 0;int head[max_data];int n, m, s;int vis[max_data];int lst[max_data][33];int deep[max_data];template<class T>void read(T &x){ int f = 0; x = 0; char ch = getchar(); while(ch < '0' || ch > '9') f |= (ch == '-'), ch = getchar(); while(ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); x = f? -x : x;}template<class T>void write(T x){ if(x < 0) x = -x, putchar('-'); if(x > 9) write(x / 10); putchar(x % 10 + '0');}void add(int u, int v){ edge[++edge_size].next = head[u]; edge[edge_size].to = v; head[u] = edge_size;}template<class T>T change(T &x, T &y){int w = x; x = y; y = w;}void dfs(int s);void lca();void init();void work();int main(){// freopen("in.txt","r",stdin); init(); work(); return 0;}void init(){ read(n);read(m);read(s); for(int i = 1; i <= n - 1; i++) { int u, v; read(u);read(v); add(u, v); add(v, u); } deep[s] = 1; dfs(s);}void dfs(int x){ vis[x] = 1; for(int i = head[x]; i; i = edge[i].next) { int temp = edge[i].to; if(!vis[temp]) { lst[temp][0] = x; deep[temp] = deep[x] + 1; for(int j = 0, last = x; lst[last][j]; last = lst[last][j], j++) lst[temp][j + 1] = lst[last][j]; dfs(temp); } }}int lca(int x, int y){ if(deep[x] < deep[y]) change(x, y); int st; for(st = 0; (1 << st) <= deep[x]; st++); st--; for(int i = st; i >= 0; i--) if(deep[lst[x][i]] >= deep[y]) x = lst[x][i]; if(x == y) return x; for(int i = st; i >= 0; i--) if(lst[x][i] != lst[y][i]) x = lst[x][i], y = lst[y][i]; return lst[x][0];}void work(){ int x, y; for(int i = 1; i <= m; i++) { read(x);read(y); write(lca(x, y)); putchar(10); }}
嗯,还是比较简单的一种算法,大家加油吧。
!注意:本篇博文以模板为主,对倍增什么的讲解并不细致,建议大家优先深入了解倍增(还有其他不细致讲解)的具体思想及其具体实现,谢谢。
博文修改记录:
- 2017.11.8 修改遗漏点(初始深度设为1)
- 2017.11.8 代码优化——if(deep[x] - (1 << st) >= deep[y]) -> if(deep[lst[x][i]] >= deep[y])——
- 【讲解+模板】最近公共祖先(LCA)(倍增)
- lca(最近公共祖先)倍增模板【pascal】
- LCA(最近公共祖先)倍增法模板及总结
- 最近公共祖先(LCA)---倍增法
- 最近公共祖先(LCA):倍增
- 【LCA倍增模板】【poj1330】最近公共祖先
- LCA----【模板】最近公共祖先(LCA)
- 【模板】最近公共祖先(LCA)
- 【模板】最近公共祖先(LCA)
- P3379 【模板】最近公共祖先(LCA)
- 【模板】【图论】最近公共祖先(LCA)
- 【模板】最近公共祖先(LCA)
- LCA最近公共祖先(朴素+倍增法)
- 最近公共祖先(LCA):tarjan与倍增
- 树上倍增求LCA(最近公共祖先)
- lca最近公共祖先(st表/倍增)
- LCA(最近公共祖先)倍增法实现
- 倍增法求最近公共祖先(LCA)
- 抽象类与接口
- AC自动机+矩阵快速幂
- ZOJ-3623 Battle Ships (完全背包 应用题)
- ZennoPoster
- seaJS 浅析(一) iUX
- 【讲解+模板】最近公共祖先(LCA)(倍增)
- #handler ,Looper的机制,分析源码(三)自己撸一套Looper机制
- 线程
- jQuery无法读取本地文件
- 异步队列发送邮件的流程
- hash_map->unordered_map
- 前端框架 seajs 使用总结
- 包装类
- 适配器模式