LCA 问题 用 Tarjan 离线算法 求解
2016-02-27 15:39
441 查看
题目:http://poj.org/problem?id=1330
思 路: LCA (最近公共祖先问题),目的是求出一棵树中,任意两个节点的最近公共祖先。
例:
则 : 4 和 7 的最近公共祖先 是 4 ;
9 和 1 的最近公共祖先 是 8 ;
3 和 2 的最近公共祖先 是 10 ;
Tarjan 算法基于 DFS + 并查集 。
分析: LCA的离线算法为Tarjan,Tarjan算法的流程如下:
DFS遍历整棵树,节点r在遍历完成之后,退回到r在树中的父节点,然后r在 并查集 中的pre指针会指向这个父节点。即对一个节点来说, 所有通过它被遍历过 的子树,都会在并查集中被 合并到这个节点所在的集合上,并以它作为代表元。
初始化每个节点各是一个集合,每次合并操作都是伴随着遍历中的退后行为进行的。 这样就产生了一个性质,在遍历过程中,一个被遍历过的节点的集合的代表元, 就是从树根到这个节点 的路径上,DFS 退后到了的那个节点。
在遍历离开当前节点之前,假设我们刚刚已经完成访问的节点是v,那么我们看与其一同被询问的另外一个点 u 是否已经被访问过了,若已经被访问过了,那么这个时候最近公共祖先必然是 u 所在集合对应的祖先 c,因为我们对 v 的访问就是从最近公共祖先 c 转过来的,并且在从c的子树 u 转向 v 的时候,我们已经将u的祖先置为了c,同时这个 c 也必然是 v 的祖先,那么c必然是u、v的最近公共祖先。
总的时间复杂度是O(n+q)的,因为DFS是O(n)的,对于询问的处理是O(q)。
思 路: LCA (最近公共祖先问题),目的是求出一棵树中,任意两个节点的最近公共祖先。
例:
则 : 4 和 7 的最近公共祖先 是 4 ;
9 和 1 的最近公共祖先 是 8 ;
3 和 2 的最近公共祖先 是 10 ;
Tarjan 算法基于 DFS + 并查集 。
分析: LCA的离线算法为Tarjan,Tarjan算法的流程如下:
DFS遍历整棵树,节点r在遍历完成之后,退回到r在树中的父节点,然后r在 并查集 中的pre指针会指向这个父节点。即对一个节点来说, 所有通过它被遍历过 的子树,都会在并查集中被 合并到这个节点所在的集合上,并以它作为代表元。
初始化每个节点各是一个集合,每次合并操作都是伴随着遍历中的退后行为进行的。 这样就产生了一个性质,在遍历过程中,一个被遍历过的节点的集合的代表元, 就是从树根到这个节点 的路径上,DFS 退后到了的那个节点。
在遍历离开当前节点之前,假设我们刚刚已经完成访问的节点是v,那么我们看与其一同被询问的另外一个点 u 是否已经被访问过了,若已经被访问过了,那么这个时候最近公共祖先必然是 u 所在集合对应的祖先 c,因为我们对 v 的访问就是从最近公共祖先 c 转过来的,并且在从c的子树 u 转向 v 的时候,我们已经将u的祖先置为了c,同时这个 c 也必然是 v 的祖先,那么c必然是u、v的最近公共祖先。
总的时间复杂度是O(n+q)的,因为DFS是O(n)的,对于询问的处理是O(q)。
#include<iostream> #include<vector> using namespace std; const int maxN=10001; vector<int> vec[maxN]; int pre[maxN]; //并查集的核心表 bool root[maxN]; // 看哪一个节点是根节点,有且只有一个 节点的值为 true bool visit[maxN]; //存放节点是否被访问的表 int u,v; void Init(int n){ //初始化 for( int i = 1;i <= n;i++ ){ pre[i] = i; //初始化每个节点都是一个集合 root[i] = true;//初始化每个节点都是根 visit[i] = false;//都未被访问 vec[i].clear();//清除上一组数据 } } int find(int n){ //查找这个节点属于哪一个集合 if( pre == n ) //如果相等,说明这个节点就是DFS当前回退到的那个节点,它还没有被更高的节点合并 return n; else //不相等,则说明 这个节点已经被 它的父节点的集合 合并了。 return pre =find( pre );//需要递归查询当前它属于哪一个集合。 } void Union(int a,int b){//合并集合。集合的大小,从下向上是逐渐增大的。 int x = find(a); int y = find(b); if( x == y ) return ; pre[y] = x; } void LCA( int r){ for( int i = 0;i < vec[r].size();i++ ){ LCA( vec[r][i] ); Union( r,vec[r][i] ); } //遍历完当前节点 r 的每一个子树之后,才会返回到 r,即后序遍历 。 //这个性质就是我们使用Tarjan算法解决最近公共祖先问题的核心思想。 visit[r] = true; if( r == u && visit[v] == true){ cout << "LCA of " << u << " and " << v << " is :" << find(pre[v]) << endl; return ; } if( r == v && visit[u] == true){ cout << "LCA of " << u << " and " << v << " is :" << find(pre[u]) << endl; return ; } } int main(void){ int T,n; int a,b; cin >> T; while(T--){ cin >> n; Init(n); for( int i = 1;i < n;i++ ){ cin >> a >> b; vec[a].push_back(b); root[b] = false; } cin >> u >> v; for( int i = 1;i <= n;i++ ){ if( root[i] == true ){ cout << "root :" << i <<endl; LCA(i); break; } } } return 0; }
相关文章推荐
- 004_Http之response响应头-02定时刷新(定时重定向)
- _PyUnicodeUCS4_AsDefaultEncodedString
- HTML5实现动画三种方式
- 1014: [JSOI2008]火星人prefix
- 基本字符串压缩
- 非常的好的协同过滤入门文章
- Android高手进阶:Adapter深入理解与优化
- MathType输入矩阵或者向量的注意事项
- [Exception JavaWeb 1] - Cause: com.microsoft.sqlserver.jdbc.SQLServerException: '@P2' 附近有语法错误。
- C# 索引器的使用
- Demo4:求职信息表
- 多方位全面解析:如何正确地写好一个界面
- 数位DP hdu3709 枚举
- Matlab JPEG详细介绍
- BZOJ3130 [Sdoi2013]费用流
- 3.1 依赖倒置原则的定义
- 键盘过滤驱动蓝屏问题
- 编译器错误信息: CS0433: 类型 同时存在于
- 【Java】----线程同步:生产-消费问题
- iOS适配的相关内容的整理