LCA
Posted dxy0310
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LCA相关的知识,希望对你有一定的参考价值。
LCA的定义:
在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,
而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点,
其实就是是两个点在这棵树上距离最近的公共祖先节点。
用途:
主要用来处理两个点有且只有一条确定的最短路径时的路径。
如何求解LCA:
1.倍增:
所谓倍增就是成倍的增长,只不过是以2为底数而已,在算法中就是成倍地跳,而不是一个一个地跳
实现:先利用dfs预处理出每个节点相对于根结点的深度length[u],
用f[u][i]表示节点u向上跳2^i所能到达的深度的节点,
依次预处理出每个节点的length和f,
再边询问边求两点的LCA,后回答。
若是询问的节点x,y不在同一个深度,则将深度更深的节点往上蹦,
直到将两者保持在同一深度。
若此时x,y在同一深度且有x==y则说明此时的x或y就是初始x,y的LCA,
直接返回就行了。
将x,y同时,同幅度地向上蹦(能够执行这一步说明并不是前一种x或y为LCA情况),
直到两者有蹦后到达的点的祖先相同,此时返回该结点的祖先即可,否则一直向上蹦。
注意:为了节约时间,更快的找到LCA,在向上跳的过程中贪心地先迈大步子后迈小步子,不过要在深度允许的范围内蹦;
f[u][i]=f[f[u][i-1]][i-1]含义为u结点向上跳2^(i-1)后到达的节点再向上跳2^(i-1)所到达的最终节点,因为2^(i-1)+2^(i-1)=2*(2^(i-1))=2^i。
在求LCA的题目中通常会给不同的边权,此时只要在dfs的同时用一个数组dis[u]记录到u点的权值和即可.
#include<bits/stdc++.h> using namespace std; #define maxn 500050 int head[maxn],length[maxn]; int f[maxn][100]; int n,m,k,ans,cnt,s; struct edge { int to,nxt; }p[maxn*2]; void add(int x,int y) { ++cnt; p[cnt].to=y; p[cnt].nxt=head[x]; head[x]=cnt; } void dfs(int u,int fa) { length[u]=length[fa]+1; f[u][0]=fa; for(int i=1;(1<<i)<=length[u];i++) f[u][i]=f[f[u][i-1]][i-1]; for(int i=head[u];i;i=p[i].nxt) { int v=p[i].to; if(v==fa) continue; dfs(v,u); } } int lca(int x,int y) { if(length[x]>length[y]) swap(x,y); for(int i=20;i>=0;i--) { if(length[x]<=length[y]-(1<<i)) y=f[y][i]; } if(x==y) return x; for(int i=20;i>=0;i--) { if(f[x][i]!=f[y][i]) { x=f[x][i],y=f[y][i]; } } return f[x][0]; } int main() { scanf("%d%d%d",&n,&m,&s); for(int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs(s,0); for(int i=1;i<=m;i++) { int a,b; scanf("%d%d",&a,&b); printf("%d ",lca(a,b)); } return 0; }
2.Tarjan
完美地利用深搜的性质以达到优化时间复杂度的效果;
1.任选一个节点为根结点,从根结点开始遍历(dfs)
2.遍历当前节点u的所有子节点v,并标记v已被访问
3.若u仍有子节点,则继续做2中步骤,知道做完(叶结点)
4.合并v到u上(利用并查集)
5.寻找与u节点有关的所有询问(u,v),若v已做完(被标记过),
则此时的LCA为v被合并的父亲节点,
因为v已做完,表示找到u的时候是从u的父结点转过来的,就是一个分叉口
Tarjan(u)//marge和find为并查集合并函数和查找函数 { for each(u,v) //访问所有u子节点v { Tarjan(v); //继续往下遍历 marge(u,v); //合并v到u上 标记v被访问过; } for each(u,e) //访问所有和u有询问关系的e { 如果e被访问过; u,e的最近公共祖先为find(e); } }
#include<bits/stdc++.h> using namespace std; #define maxn 5000050 inline int read() { int x=0,f=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) f=-1; ch=getchar(); } while(ch>=‘0‘&&ch<=‘9‘) { x=(x<<1)+(x<<3)+(ch-‘0‘); ch=getchar(); } return x*f; } int cnt,cntt,n,m,s; int head[maxn],fa[maxn],headd[maxn],ans[maxn],pre[maxn]; bool vis[maxn]; struct edge { int to,nxt; }p[maxn*2],q[maxn*2]; void addedge(int x,int y) { ++cnt; p[cnt].nxt=head[x]; p[cnt].to=y; head[x]=cnt; } void addquery(int x,int y) { ++cntt; q[cntt].to=y; q[cntt].nxt=headd[x]; headd[x]=cntt; } int find(int x) { return fa[x]==x ? fa[x] : fa[x]=find(fa[x]); } void unionn(int a,int b) { int x=find(a); int y=find(b); if(x!=y) fa[y]=x; } void Tarjan(int x) { for(int i=head[x];i;i=p[i].nxt) { int v=p[i].to; if(v!=pre[x]) { pre[v]=x; Tarjan(v); unionn(x,v); vis[v]=1; } //cout<<"yes"<<endl; } for(int i=headd[x];i;i=q[i].nxt) { int v=q[i].to; if(vis[v]) { ans[i]=find(v); // cout<<find(v)<<endl; } } } int main() { scanf("%d%d%d",&n,&m,&s); for(int i=1;i<n;i++) { int a,b; a=read(); b=read(); // scanf("%d%d",&a,&b); addedge(a,b); addedge(b,a); } for(int i=1;i<=m;i++) { int a,b; a=read(); b=read(); //scanf("%d%d",&a,&b); addquery(a,b); addquery(b,a); } for(int i=1;i<=n;i++) { fa[i]=i; pre[i]=i; } Tarjan(s); // cout<<"yes"<<endl; for(int i=1;i<=m;i++) printf("%d ",max(ans[2*i-1],ans[2*i])); return 0; }
以上是关于LCA的主要内容,如果未能解决你的问题,请参考以下文章
代码源 Div1 - 105#451. Dis(倍增求LCA)