POJ1330Nearest Common Ancestors

来源:互联网 发布:《网络基础知识》文档 编辑:程序博客网 时间:2024/06/05 09:29

题意

第一行输入T,有T组数据。
对于每组数据,给出一棵树,先输入n,然后n-1行,每行两个数a,b,表示a是b的父亲;第n行输入两个数A,B表示询问A和B的最近公共祖先。

题解

LCA模板题。建议先学学LCA

 

有两种方法,分别是Tarjan和倍增,这里说一说倍增。
LCA_倍增是LCA的在线算法,时间和空间复杂度分别是O((n+q)log n)和O(n log n)。
对于这个算法,我们从最暴力的算法开始:
①如果a和b深度不同,先把深度调浅,使他变得和浅的那个一样
②现在已经保证了a和b的深度一样,所以我们只要把两个一起一步一步往上移动,直到他们到达同一个节点,也就是他们的最近公共祖先了。

O(nq)的暴力实现

#include <cstring>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cmath>#include <vector>using namespace std;const int N=10000+5;vector <int> son[N];int T,n,depth[N],fa[N],in[N],a,b;void dfs(int prev,int rt){    depth[rt]=depth[prev]+1;    fa[rt]=prev;    for (int i=0;i<son[rt].size();i++)        dfs(rt,son[rt][i]);}int LCA(int a,int b){    if (depth[a]>depth[b])        swap(a,b);    while (depth[b]>depth[a])        b=fa[b];    while (a!=b)        a=fa[a],b=fa[b];    return a;}int main(){    scanf("%d",&T);    while (T--){        scanf("%d",&n);        for (int i=1;i<=n;i++)            son[i].clear();        memset(in,0,sizeof in);        for (int i=1;i<n;i++){            scanf("%d%d",&a,&b);            son[a].push_back(b);            in[b]++;        }        depth[0]=-1;        int rt=0;        for (int i=1;i<=n&&rt==0;i++)            if (in[i]==0)                rt=i;        dfs(0,rt);        scanf("%d%d",&a,&b);        printf("%d\n",LCA(a,b));    }    return 0;}

 

而实际上,一步一步往上移动太慢,我们可以做一个预处理:
fa[i][j]表示节点i往上走2^j次所到达的祖先,那么不难写出转移方程:
fa[i][0]=father[i],fa[i][j]=fa[fa[i][j-1]][j-1]
然后在求LCA的时候,有这样一个性质:(假设a和b深度一样)
设anst[x][y]为节点x网上走y步到达的祖先,对于一个k,如果anst[a][k]==anst[b][k],那么对于k'(k'>k),一定有anst[a][k']==anst[b][k'];对于一个k,如果anst[a][k]!=anst[b][k],那么对于k'(k'<k),一定有anst[a][k']!=anst[b][k'],而且LCA(a,b)=LCA(anst[a][k],anst[b][k])。
于是求法就渐渐的现行了:
1. 把a和b移到同一深度(设depth[x]为节点x的深度),假设depth[a]<=depth[b],所以我们的目的是把b向上移动i=(depth[b]-depth[a])层,那么,由于之前有预处理的fa数组,我们把i写成二进制形势,然后利用fa数组来在log n的复杂度中完成;
2. 寻找a和b的LCA下一层的两个祖先。利用之前的那个性质,再利用倍增,如果a和b的第2^k个祖先不是同一个,那么把a改为fa[a][k],b改为fa[b][k],k减1;否则直接k减1;当然在这之前要实现确定k的最大值,从大往小处理下去。最终的结果就是fa[a][0]或者fa[b][0]。
注意点:如果a和b在调节深度之后已经是同一个祖先的,那么直接返回a或者b。

LCA倍增算法&POJ1330标程

 

#include <cstring>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cmath>#include <vector>using namespace std;const int N=10000+5;vector <int> son[N];int T,n,depth[N],fa[N][17],in[N],a,b;void dfs(int prev,int rt){    depth[rt]=depth[prev]+1;    fa[rt][0]=prev;    for (int i=1;(1<<i)<=depth[rt];i++)        fa[rt][i]=fa[fa[rt][i-1]][i-1];    for (int i=0;i<son[rt].size();i++)        dfs(rt,son[rt][i]);}int LCA(int a,int b){    if (depth[a]>depth[b])        swap(a,b);    for (int i=depth[b]-depth[a],j=0;i>0;i>>=1,j++)        if (i&1)            b=fa[b][j];    if (a==b)        return a;    int k;    for (k=0;(1<<k)<=depth[a];k++);    for (;k>=0;k--)        if ((1<<k)<=depth[a]&&fa[a][k]!=fa[b][k])            a=fa[a][k],b=fa[b][k];    return fa[a][0];}int main(){    scanf("%d",&T);    while (T--){        scanf("%d",&n);        for (int i=1;i<=n;i++)            son[i].clear();        memset(in,0,sizeof in);        for (int i=1;i<n;i++){            scanf("%d%d",&a,&b);            son[a].push_back(b);            in[b]++;        }        depth[0]=-1;        int rt=0;        for (int i=1;i<=n&&rt==0;i++)            if (in[i]==0)                rt=i;        dfs(0,rt);        scanf("%d%d",&a,&b);        printf("%d\n",LCA(a,b));    }    return 0;}

 

原创粉丝点击