最近公共祖先LCA的求法
Posted dsb-y
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最近公共祖先LCA的求法相关的知识,希望对你有一定的参考价值。
一道例题:
最近公共祖先
Description
给你一棵有根树,要求你计算出m对结点的最近公共祖先。
Input
输入文件的第一行包含两个整数n和m(2<=n,m<=200,000),其中n为结点个数,结点编号为1到n;m表示询问次数。
接下来n-1行,每行两个整数x和y,表示结点x是结点y的父亲结点;
接下来的m行,每行两个整数a和b,要求计算出结点a和b的最近公共祖先。
接下来n-1行,每行两个整数x和y,表示结点x是结点y的父亲结点;
接下来的m行,每行两个整数a和b,要求计算出结点a和b的最近公共祖先。
Output
输出文件共m行,分别为每对结点的最近公共祖先编号。
Sample Input
5 3
2 3
3 4
3 1
1 5
3 5
4 5
1 2
Sample Output
3
3
2
一.倍增法
最常见也是最实用的方法,可在线询问
时间复杂度O(nlogn)
每个询问的复杂度O(logn)
#include<iostream> #include<algorithm> #include<cmath> #include<cstring> using namespace std; int n,m,cnt=0; struct node1{int to,next;}a[200005]; int h[200005],prt[200005],f[200005][25],d[200005]; void add(int x,int y){cnt++;a[cnt].to=y;a[cnt].next=h[x];h[x]=cnt;} void dfs(int x,int deep) { d[x]=deep; int i; for(i=h[x];i;i=a[i].next) dfs(a[i].to,deep+1); } void ST() { memset(f,-1,sizeof(f)); int i,j; for(i=1;i<=n;i++)f[i][0]=prt[i]; for(j=1;(1<<j)<=n;j++) for(i=1;i<=n;i++) if(f[i][j-1]!=-1)f[i][j]=f[f[i][j-1]][j-1]; } int LCA(int x,int y) { int i,j,k; if(d[x]<d[y])swap(x,y); k=int(log(d[x])/log(2)); for(i=k;i>=0;i--) if(d[x]-(1<<i)>=d[y])x=f[x][i]; if(x==y)return y; for(i=k;i>=0;i--) if(f[x][i]!=-1&&f[x][i]!=f[y][i]){x=f[x][i];y=f[y][i];} return f[x][0]; } int main() { int x,y,i,j,root; cin>>n>>m; for(i=1;i<=n-1;i++) { scanf("%d%d",&x,&y); add(x,y); prt[y]=x; } root=1; while(prt[root]!=0)root=prt[root]; dfs(root,1); ST(); for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); printf("%d ",LCA(x,y)); } return 0; }
二.tarjan求法
此坑待填
三.树链剖分求法
支持在线。
时间复杂度O(nlogn),一般比倍增法快
每个询问的复杂度O(logn)
#include<iostream> #include<queue> #include<cstdio> #include<cstring> #define ll long long using namespace std; int n,m,d[200005],prt[200005],cnt,son[200005],size[200005],tot,h[200005],top[200005]; struct node{int to,next;}a[200005]; void add(int x,int y){cnt++;a[cnt].to=y;a[cnt].next=h[x];h[x]=cnt;} void dfs1(int x,int fa,int deep) { int i,j,maxsize=0; d[x]=deep; size[x]=1; for(i=h[x];i;i=a[i].next) { j=a[i].to; dfs1(j,x,deep+1); if(size[j]>maxsize)maxsize=size[j],son[x]=j; size[x]+=size[j]; } } void dfs2(int x,int anc) { int i,j; top[x]=anc; if(son[x]!=-1)dfs2(son[x],anc); for(i=h[x];i;i=a[i].next) { j=a[i].to; if(j==son[x])continue; dfs2(j,j); } } int ask(int x,int y) { int f1=top[x],f2=top[y]; while(f1!=f2) { if(d[f1]<d[f2])swap(f1,f2),swap(x,y); x=prt[f1];f1=top[x]; } if(x==y)return x; if(d[x]>d[y])swap(x,y); return x; } int main() { int i,j,k,x,y,root; scanf("%d%d",&n,&m); for(i=2;i<=n;i++)scanf("%d%d",&x,&y),add(x,y),prt[y]=x; for(i=1;i<=n;i++)if(!prt[i]){root=i;break;} memset(son,-1,sizeof(son)); dfs1(root,0,1); dfs2(root,root); for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); printf("%d ",ask(x,y)); } return 0; }
三.欧拉序求法
支持在线。
时间复杂度O(nlogn),
每个询问的复杂度O(1)
#include<iostream> #include<queue> #include<cstdio> #include<cmath> #define ll long long using namespace std; int n,m,d[200005],prt[200005],cnt,tot,h[200005]; struct node{int to,next;}a[200005]; void add(int x,int y){cnt++;a[cnt].to=y;a[cnt].next=h[x];h[x]=cnt;} int f[400005][20],g[400005][20]; int q[400005]; int td1[200005],td2[200005]; void dfs(int x,int deep) { int i,j; d[x]=deep; q[++tot]=x; td1[x]=tot;td2[x]=tot; for(i=h[x];i;i=a[i].next) { j=a[i].to; dfs(j,deep+1); q[++tot]=x;td2[x]=tot; } } int ask(int L,int r) { int k=log(r-L+1)/log(2); if(g[L][k]<g[r-(1<<k)+1][k])return f[L][k]; else return f[r-(1<<k)+1][k]; } int main() { int i,j,k,x,y,root; scanf("%d%d",&n,&m); for(i=2;i<=n;++i)scanf("%d%d",&x,&y),add(x,y),prt[y]=x; for(i=1;i<=n;++i)if(!prt[i]){root=i;break;} dfs(root,1); for(i=1;i<=tot;i++)f[i][0]=q[i],g[i][0]=d[q[i]]; for(j=1;(1<<j)<=tot;j++) for(i=1;i<=tot;i++) { if(g[i][j-1]<g[i+(1<<(j-1))][j-1]) f[i][j]=f[i][j-1],g[i][j]=g[i][j-1]; else f[i][j]=f[i+(1<<(j-1))][j-1],g[i][j]=g[i+(1<<(j-1))][j-1]; } for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); if(td1[x]>td2[y])swap(x,y); printf("%d ",ask(td1[x],td2[y])); } return 0; }
以上是关于最近公共祖先LCA的求法的主要内容,如果未能解决你的问题,请参考以下文章