倍增求LCA

血红的双手。 提交于 2019-12-01 19:58:00

P3379 【模板】最近公共祖先(LCA)

【题目大意】

给你树的节点个数\(n\),询问个数\(m\)和树根\(s\),
输入\(n,m,s\);
输入\(n\)\(x,y.\)表示\(x,y\)结点之间有一条边
输入\(m\)\(a,b.\)表示求\(a,b\)的最近公共祖先

【思路】

下面的解释均以这个图为例


今天用这道题来说一下倍增,先预处理出每个节点的深度,和lg数组,就是\(log_2{i}\)的值.所谓倍增,就是按\(2\)的倍数来增大,也就是跳\(1,2,4,8,16,32……\)不过在这我们不是按从小到大跳,而是从大向小跳,即按\(……32,16,8,4,2,11\)来跳,如果大的跳不过去,再把它调小。这是因为从小开始跳,可能会出现“悔棋”的现象。拿\(5\)为例,从小向大跳,\(5≠1+2+4\),所以我们还要回溯一步,然后才能得出\(5=1+4\);而从大向小跳,直接可以得出\(5=4+1\)。这也可以拿二进制为例,\(5(101)\),从高位向低位填很简单,如果填了这位之后比原数大了,那我就不填,这个过程是很好操作的。
还是以\(17\)\(18\)为例(此例只演示倍增,并不是倍增LCA算法的真正路径)
\(17->3\)
\(18->5->3\)
可以看出向上跳的次数大大减小。这个算法的时间复杂度为\(O(nlogn)\),已经可以满足大部分的需求。
想要实现这个算法,首先我们要记录各个点的深度和他们\(2^{i}\)级的的祖先,用数组\(depth\)表示每个节点的深度,\(fa[i][j]\)表示节点\(i\)\(2^j\)级祖先。 代码如下:
void dfs(int f,int fath)//f表示当前节点,fath表示他的父亲 {     depth[f]=depth[fath]+1;     fa[f][0]=fath;     for(int i=1;(1<<i)<=depth[f];i++)       fa[f][i]=fa[fa[f][i-1]][i-1]; //这个转移是核心.意思是f的2^i祖先等于f的2^(i-1)祖先的2^(i-1)祖先 //2^i=2^(i-1)*2^(i-1)      for(int i=head[f];i;i=e[i].nex)       if(e[i].t!=fath)         dfs(e[i].t,f); }

预处理完毕后,我们就可以去找它的\(LCA\)了,为了让它跑得快一些,我们可以加一个常数优化

for(int i=1;i<=n;i++) //预先算出log_2(i)+1的值,用的时候直接调用就可以了   lg[i]=lg[i-1]+(1<<lg[i-1]==i);  //看不懂的可以手推一下

接下来就是倍增\(LCA\)了,我们先把两个点提到同一高度,再统一开始跳。
但我们在跳的时候不能直接跳到它们的\(LCA\),因为这可能会误判,比如\(4\)\(8\),在跳的时候,我们可能会认为\(1\)是它们的\(LCA\),但\(1\)只是它们的祖先,它们的\(LCA\)其实是\(3\)。所以我们要跳到它们\(LCA\)的下面一层,比如\(4\)\(8\),我们就跳到\(4\)\(5\),然后输出它们的父节点,这样就不会误判了。

int lca(int x,int y) {     if(depth[x]<depth[y])//保证x比y深.便于一会调到同一深度       swap(x,y);     while(depth[x]>depth[y])//调到同一深度.       x=fa[x][lg[depth[x]-depth[y]]-1];     if(x==y)//如果到了同一个点了,那就到了lca了.       return x;     for(int k=lg[depth[x]]-1;k>=0;k--)//不断往上调.       if(fa[x][k]!=fa[y][k])//如果两个父亲不一样.          x=fa[x][k],y=fa[y][k];//继续往上跳     return fa[x][0];//返回 }

完整的求\(17\)\(18\)的LCA的路径:
\(17->10->7->3\)
\(18->16->8->5->3\)
解释:首先,1818要跳到和1717深度相同,然后1818和1717一起向上跳,一直跳到LCA的下一层(1717是77,1818是55),此时LCA就是它们的父亲
这个题就是这样.so easy.
完整代码

#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<string> #include<algorithm> #include<iomanip> #include<cstdlib> #include<queue> #include<map> #include<set> #include<stack> #include<vector> #define ll long long using namespace std; inline int read() {    int s=0,w=1;    char ch=getchar();    while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}    while(isdigit(ch)) s=s*10+ch-'0',ch=getchar();    return s*w; } struct yyy {     int t,nex; }e[2*500001]; int depth[500001],fa[500001][22],lg[500001],head[500001]; int tot; void add(int x,int y) {     e[++tot].t=y;      e[tot].nex=head[x];     head[x]=tot; } void dfs(int f,int fath) {     depth[f]=depth[fath]+1;     fa[f][0]=fath;     for(int i=1;(1<<i)<=depth[f];i++)       fa[f][i]=fa[fa[f][i-1]][i-1];     for(int i=head[f];i;i=e[i].nex)       if(e[i].t!=fath)         dfs(e[i].t,f); } int lca(int x,int y) {     if(depth[x]<depth[y])       swap(x,y);     while(depth[x]>depth[y])       x=fa[x][lg[depth[x]-depth[y]]-1];     if(x==y)       return x;     for(int k=lg[depth[x]]-1;k>=0;k--)       if(fa[x][k]!=fa[y][k])         x=fa[x][k],y=fa[y][k];     return fa[x][0]; } int n,m,s; int main() {     n=read(),m=read(),s=read();     for(int i=1,x,y;i<=n-1;i++)         x=read(),y=read(),add(x,y),add(y,x);     dfs(s,0);     for(int i=1;i<=n;i++)       lg[i]=lg[i-1]+(1<<lg[i-1]==i);     for(int i=1,x,y;i<=m;i++)         x=read(),y=read(),printf("%d\n",lca(x,y));     return 0; }

倍增也就是这个思想了吧.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!