LCA-Least Common Ancestors(最近公共祖先)
2018-03-26 22:38
405 查看
问题类型
给定一棵有根多叉树,给出指定的两个结点的最近公共祖先求解方法
倍增Tarjan(并查集+DFS)
RMQ(Range Minimum Query,区间最小值查询+时间戳
倍增法
用倍增优化之前的代码:struct edge//链式前向星 { int to; int next; }; edge e[1000010]; int head[500010]; int cnt = 1; int n,m,s; int deepth[500010];//结点的深度 int father[500010];//父结点 bool book[500010];//标记数组 void add_edge(int u,int v) { e[cnt].to = v; e[cnt].next = head[u]; head[u] = cnt; cnt++; } void dfs(int st,int step)//当前结点,树的深度 { book[st] = true; deepth[st] = step; for(int i = head[st]; i != -1; i = e[i].next) { int to = e[i].to; if(book[to] == false)//如果没被标记 { father[to] = st;//更新父亲 dfs(to,step + 1);//继续搜索 } } return; } int Lca(int a,int b) { if(deepth[a] < deepth)//这样就可以只写一个while循环 swap(a,b); while(deepth[a] > deepth[b]) a = father[a]; while(a != b) { //不相等就向上移动 a = father[a]; b = father[b]; } return a;//返回的是a,不是father[a] } int main() { cin >> n >> m >> s;//结点数、询问数、根结点 memset(head,-1,sizeof(head));//初始化 for(int i = 1; i <= n - 1; ++i)//建树 { int a,b; scanf("%d%d",&a,&b); add_edge(a,b); add_edge(b,a); } father[s] = s;//初始化根的父亲为它自己 dfs(s,1);//搜索,求出深度和父亲 for(int i = 1; i <= m; ++i)//查询 { int a,b; scanf("%d%d",&a,&b); printf("%d\n",Lca(a,b)); } return 0; }
[b]倍增代码:
struct edge//链式前向星 { int to; int next; }; edge e[1000010]; int head[500010]; int cnt = 1; int n,m,s; int book[500010]; int deepth[500010]; int dp[500010][25];//dp[i][j]表示结点i向上跳2^j步所到达的结点 void add_edge(int u,int v)//建边 { e[cnt].to = v; e[cnt].next = head[u]; head[u] = cnt; cnt++; } void dfs(int st,int step)//当前结点,树的深度 { book[st] = true;//标记 deepth[st] = step; for(int i = head[st]; i != -1; i = e[i].next) { int to = e[i].to; if(book[to] == false)//如果没被标记 { dp[to][0] = st;//更新父亲 dfs(to,step + 1);//继续搜索 } } return; } void calc_dp() { int max_step = log(n) / log(2);//最大的一步可以跳多少。考虑树是一条直链时的极限情况,这时候最深的结点最大的一步能向上跳{2的[log2(n)取下整]次方}步。我们的步子是2的幂次,所以要这样取值 for(int i = 1; i <= max_step; ++i)//一个步子的范围为[1,n],即[2^0,2^log2(n)],又因为dp[][0]已被求出,所以i从1开始 { for(int j = 1; j <= n; ++j)//对于结点j { if(dp[j][i - 1] != -1)//如果没越界,很重要! dp[j][i] = dp[dp[j][i - 1]][i - 1]; } } return; } int Lca(int a,int b) { if(deepth[a] < deepth)//令a为较深的结点 swap(a,b); int max_s = log(deepth[a]) / log(2);//结点a能跳的最大的一步为2^max_s for(int i = max_s; i >= 0; --i)//从最大的一步开始跳,循环结束后,a和b必位于同一层 if(deepth[a] - pow(2,i) >= deepth[b])//如果跳完了深度依然大于等于b,就跳。 a = dp[a][i]; if(a == b)//如果相等,说明当前结点就是最近公共祖先 return a; for(int i = max_s; i >= 0; --i) { //跳到最近公共祖先的子节点处 if(dp[a][i] != -1 && dp[a][i] != dp[b][i])//一起向上跳,如果没有越界且跳完后的结点不相同,就跳。相等就不跳了 { a = dp[a][i]; b = dp[b][i]; } } return dp[a][0]; } int main() { cin >> n >> m >> s;//结点数、询问数、根结点 memset(head,-1,sizeof(head));//初始化 for(int i = 1; i <= n - 1; ++i)//建树 { int a,b; scanf("%d%d",&a,&b); add_edge(a,b); add_edge(b,a); } memset(dp,-1,sizeof(dp));//初始化 dp[s][0] = s;//将根结点的父亲初始化为它本身 dfs(s,1);//求出每个结点的深度以及dp的边界值(父亲) calc_dp();//计算 for(int i = 1; i <= m; ++i) { int a,b; scanf("%d%d",&a,&b); printf("%d\n",Lca(a,b)); } return 0; }
[b]解决方法:
在树中,除了根结点,每一个结点有且只有一个父亲,我们可以记录结点i的父亲为结点father[i],这样我们就可以根据父亲结点向上移动,直到遇到相同的父亲结点,即为最近公共祖先。考虑到结点a和结点b很有可能不在同一深度上,于是用deepth数组记录结点在树中的深度,深度大的主动移动到深度小的结点的同一深度水平上,再进行向上移动。显然,对树的遍历可以用搜索解决。
以上未用倍增优化,超时。
如果只是用一次的话,一般的LCA也就够了,它的时间复杂度为O(n) ,而倍增优化的Lca的时间复杂度为O(logn)
若干结论:
a与b的最近公共祖先一定位于同一深度上(显然)
任何一个数都可以表示成2的幂次之和的形式
dp[i][j]用递推来求出,因为其子问题
dp[i][j - 1]已被求出,而
dp[i][j] = dp[[i][j - 1]][j - 1]
到达了同一结点后,不论再怎么向上走,到达的显然还是同一结点
RMQ+时间戳
代码:这里写代码片
解决方法:
对于涉及有根树的问题,将树转为从根DFS标号后得到的序列处理的技巧常常十分有效
相关文章推荐
- 最近公共祖先 Least Common Ancestors(LCA)算法 --- 与RMQ问题的转换
- LCA问题——最近公共祖先(Least Common Ancestors)
- 最近公共祖先(least common ancestors,LCA)
- LCA最近公共祖先(least common ancestors)
- LCA最近公共祖先(least common ancestors)
- LCA(Least Common Ancestors)最近公共祖先
- 最近公共祖先(Least Common Ancestors,LCA)问题详解
- LCA(Least Common Ancestors)最近公共祖先问题
- LCA(least common ancestors)最近公共祖先
- POJ1330 Nearest Common Ancestors【最近公共祖先】【Tarjan-LCA算法】
- 【POJ】1330 Nearest Common Ancestors ——最近公共祖先(LCA)
- 最近公共祖先(Least Common Ancestors)
- 最近公共祖先(Least Common Ancestors)
- 【LCA】最近公共祖先问题Lowest Common Ancestors
- 最近公共祖先(Least Common Ancestors)
- POJ1330 Nearest Common Ancestors(最近公共祖先LCA 并查集+DFS)
- poj 1330 Nearest Common Ancestors(最近公共祖先(LCA))
- POJ1470 Closest Common Ancestors(最近公共祖先lca,离线Tarjan)
- 最近公共祖先(least common ancestors algorithm)
- poj 1330 Nearest Common Ancestors(LCA:最近公共祖先)