LCA 转 RMQ算法 【总结】
来源:互联网 发布:西门子840dsl编程手册 编辑:程序博客网 时间:2024/06/08 01:17
首先,在你看这个算法之前,要确保你理解了RMQ的 ST 算法。
但是不理解没关系啊,提供通道: 点我
声明:这是方便我以后复习用的,所以总结不是特别详细。
LCA - 最近公共祖先:在有根树中,两个节点u和v的公共祖先中距离最近的那个点。
上图理解:
如图,我们想要求出LCA(4,7),LCA(8,6),LCA(5,8)。
用LCA转RMQ思想 如此实现。
一:按从根DFS访问的顺序得到顶点序列vs[ i ] 和 对应的深度depth[ i ](两者下标是一一对应的)。对于每个顶点 v ,记其在vs中首次出现的下标为id[ v ]。
vs[ i ] 代表第i次DFS遍历的节点编号
depth[ i ]代表第i次DFS遍历的节点深度
id[ i ]代表节点 i 在vs中第一次出现的下标。
这里运用了时间戳dfs_clock 记录DFS次数。
代码实现:
#include <cstdio>#include <cstring>#include <queue>#include <algorithm>#define MAXN 1010#define MAXM 100000using namespace std;struct Edge{ int from, to, next;};Edge edge[MAXM];int head[MAXN], edgenum;int vs[MAXN<<1];//第i次DFS访问节点的编号int depth[MAXN<<1];//第i次DFS访问节点的深度int id[MAXN];//id[i] 记录在vs数组里面 i节点第一次出现的下标int dfs_clock;//时间戳int N, M;//点数 边数int dp[MAXN<<1][20];//dp[i][j]存储depth数组 以下标i开始的,长度为2^j的区间里 最小值所对应的下标void init(){ edgenum = 0; memset(head, -1, sizeof(head));}void addEdge(int u, int v){ Edge E = {u, v, head[u]}; edge[edgenum] = E; head[u] = edgenum++;}void getMap(){ int a, b; while(M--) scanf("%d%d", &a, &b), addEdge(a, b), addEdge(b, a);}void DFS(int u, int fa, int d)//当前遍历点以及它的父节点 遍历点深度{ id[u] = dfs_clock; vs[dfs_clock] = u; depth[dfs_clock++] = d; for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if(v == fa) continue; DFS(v, u, d+1); vs[dfs_clock] = u;//类似 回溯 depth[dfs_clock++] = d; }}void find_depth(){ dfs_clock = 1; memset(vs, 0, sizeof(vs)); memset(id, 0, sizeof(id)); memset(depth, 0, sizeof(depth)); DFS(1, -1, 0);//遍历}void input(){ printf("下标: "); for(int i = 1; i < dfs_clock; i++) printf("%d ", i); printf("\n"); printf("vs: "); for(int i = 1; i < dfs_clock; i++) printf("%d ", vs[i]); printf("\n"); printf("depth: "); for(int i = 1; i < dfs_clock; i++) printf("%d ", depth[i]); printf("\n"); printf("下标: "); for(int i = 1; i <= N; i++) printf("%d ", i); printf("\n"); printf("id: "); for(int i = 1; i <= N; i++) printf("%d ", id[i]); printf("\n");}int main(){ while(scanf("%d%d", &N, &M) != EOF) { init(); getMap(); find_depth();//DFS遍历整个树 求出所需要的信息 input();//输出查找信息 } return 0;}
输入数据:
8 71 21 32 42 53 65 75 8
输出:(没有对齐)
模拟上图得如下结果:
i12345678id1621373108
二:LCA(u,v)就是访问u之后到访问v之前所经过顶点中离根最近的点。
假设id[ u ] <= id[ v ],那么有
LCA(u,v)= vs[ id[ u ] <= i <= id[ v ]中depth[ i ]最小的 i ]。
对于这个RMQ可以这样实现:
RMQ_init(dfs_clock - 1);int dp[MAXN<<1][20];//dp[i][j]存储depth数组 以下标i开始的,长度为2^j的区间里 最小值所对应的下标void RMQ_init(int NN)//预处理 区间最小值{ for(int i = 1; i <= NN; i++) dp[i][0] = i; for(int j = 1; (1<<j) <= NN; j++) { for(int i = 1; i + (1<<j) - 1 <= NN; i++) { int a = dp[i][j-1]; int b = dp[i + (1<<(j-1))][j-1]; if(depth[a] <= depth[b])//比较的下标所对应的值 dp[i][j] = a;//更新下标 else dp[i][j] = b; } }}int query(int L, int R){ //查询L <= i <= R 里面使得depth[i]最小的值 返回对应下标 int k = 0; while((1<<(k+1)) <= R-L+1) k++; int a = dp[L][k]; int b = dp[R - (1<<k) + 1][k]; if(depth[a] <= depth[b]) return a;//返回下标 else return b;}int LCA(int u, int v){ int x = id[u];//比较大小 小的当作左区间 大的当作右区间 int y = id[v]; if(x > y) return vs[query(y, x)]; else return vs[query(x, y)];}
练习:给你N个点、M条边和Q次查询(保证是一棵树)。查询a b 的LCA。
代码实现:
#include <cstdio>#include <cstring>#include <queue>#include <algorithm>#define MAXN 1010#define MAXM 100000using namespace std;struct Edge{ int from, to, next;};Edge edge[MAXM];int head[MAXN], edgenum;int vs[MAXN<<1];//第i次DFS访问节点的编号int depth[MAXN<<1];//第i次DFS访问节点的深度int id[MAXN];//id[i] 记录在vs数组里面 i节点第一次出现的下标int dfs_clock;//时间戳int N, M, Q;//点数 边数 查询数int dp[MAXN<<1][20];//dp[i][j]存储depth数组 以下标i开始的,长度为2^j的区间里 最小值所对应的下标void init(){ edgenum = 0; memset(head, -1, sizeof(head));}void addEdge(int u, int v){ Edge E = {u, v, head[u]}; edge[edgenum] = E; head[u] = edgenum++;}void getMap(){ int a, b; while(M--) scanf("%d%d", &a, &b), addEdge(a, b), addEdge(b, a);}void DFS(int u, int fa, int d)//当前遍历点以及它的父节点 遍历点深度{ id[u] = dfs_clock; vs[dfs_clock] = u; depth[dfs_clock++] = d; for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if(v == fa) continue; DFS(v, u, d+1); vs[dfs_clock] = u;//类似 回溯 depth[dfs_clock++] = d; }}void find_depth(){ dfs_clock = 1; memset(vs, 0, sizeof(vs)); memset(id, 0, sizeof(id)); memset(depth, 0, sizeof(depth)); DFS(1, -1, 0);//遍历}void RMQ_init(int NN)//预处理 区间最小值{ for(int i = 1; i <= NN; i++) dp[i][0] = i; for(int j = 1; (1<<j) <= NN; j++) { for(int i = 1; i + (1<<j) - 1 <= NN; i++) { int a = dp[i][j-1]; int b = dp[i + (1<<(j-1))][j-1]; if(depth[a] <= depth[b]) dp[i][j] = a; else dp[i][j] = b; } }}int query(int L, int R){ //查询L <= i <= R 里面使得depth[i]最小的值 返回对应下标 int k = 0; while((1<<(k+1)) <= R-L+1) k++; int a = dp[L][k]; int b = dp[R - (1<<k) + 1][k]; if(depth[a] <= depth[b]) return a; else return b;}int LCA(int u, int v){ int x = id[u];//比较大小 小的当作左区间 大的当作右区间 int y = id[v]; if(x > y) return vs[query(y, x)]; else return vs[query(x, y)];}void solve(){ int a, b; while(Q--) { scanf("%d%d", &a, &b); printf("LCA(%d %d) = %d\n", a, b, LCA(a, b)); }}int main(){ while(scanf("%d%d%d", &N, &M, &Q) != EOF) { init(); getMap(); find_depth();//DFS遍历整个树 求出所需要的信息 RMQ_init(dfs_clock - 1); solve(); } return 0;}
8 7 31 21 32 42 53 65 75 84 78 65 8
输出:
LCA(4 7) = 28 6LCA(8 6) = 15 8LCA(5 8) = 5
毕竟是菜鸟,不好之处请见谅。当然若有不对的地方欢迎指正。
累死了。。。
0 0
- LCA 转 RMQ算法 【总结】
- RMQ与LCA算法总结
- LCA 转 RMQ算法 【知识点】
- RMQ与LCA的算法总结
- LCA转RMQ 在线算法模板
- LCA与RMQ算法
- lca rmq 算法资料
- LCA与RMQ算法
- LCA问题----RMQ算法
- RMQ和LCA总结
- 【lca】lca转rmq poj1330
- 【lca】lca转rmq解法
- 算法之LCA和RMQ
- RMQ和LCA在线算法
- LCA转RMQ
- lca转rmq
- poj1330 lca转rmq
- RMQ转LCA
- Java加密与解密的艺术-读书笔记1-2章
- 【LightOJ 1002】 Country Roads
- 连通性小结(贴一些模板而已)
- spring中scope作用域(转)
- 诸葛孔明的简洁之美
- LCA 转 RMQ算法 【总结】
- 原来新建一个类,需要那么长的代码,瞬间觉得有点疲惫了 ~
- hdu-2037/akoj-1251-今年暑假不AC(C#结构体排序)
- leetcode:Product of Array Except Self
- 一步一步学Android
- HDU 3746 Cyclic Nacklace 利用 KMP的next数组求循环节
- linux系统的颜色配置
- http协议学习和总结系列
- canvas的基本使用