【原创】【LCA】求最近公共祖先的三种方法(一)倍增 ※【USACO MAR11银组】聚会地点

利用倍增求LCA

LCA的定义

LCA就是树上两个点的最近公共祖先,
注意:自己和自己的LCA是自己
好,进入正题,倍增。



倍增

倍增有点像树状数组。。。

最重要的定义

定义 f[i][j]i2j20
这个也很好求,

f[i][j]=f[f[i][j-1]][j-1]
边界条件:f[i][0]为i的父亲结点

可以直接初始化出来。
值得注意的是,递推式只与j-1层有关,所以先循环j,内嵌i。
为了速度也可以交换i和j的意义。

j的范围是0~log2(n)。






两个基本的操作

往上走任意步

定义函数getk(x,k),返回结点x第k个祖先的编号。
其实也很好写,把k拆成二进制,然后用f数组往上走。

int getk(int x,int k)
{
    for(int i=0;i<LOG;i++)
        if(k&(1<<i)) x=f[x][i];
    return x;
}

嗯。
LOG是一个常数,比log2(n)稍大一点。(用以开数组)


往上走到任意深度

定义函数getd(x,d),返回x的哪个祖先深度为d。
就更好写了,用深度差就可以确定要走到第几个祖先,然后getk。
注意是往上走,不要往下走走到任意深度。

int getd(int x,int d)
{
    return getk(x,depth[x]-d);
}

//depth[x]代表x的深度,求深度一个简单的dfs就OK了





求LCA

方法一:笨办法

两个结点依次往上走走到根,生成两个数列。从两条序列的末尾往前走,走到最后一个相同的结点,就是LCA。
如:
2 3 4 5 6 7
9 8 1 5 6 7

7相同,6相同,5相同,4和1不同,所以5是LCA。

时间复杂度: dfsΘ(n),Θ(n)Θ(n)
有人问我为什么不能从前往后找到第一个相同的就是LCA。
这样是可以的,但是如果两个序列长度不一样,就很不方便。

所以,为什么不能让序列一样长呢?
于是,自然地,我们要先统一高度。



方法二:二分

我们有高效的getd算法,怎么用?
我们自然就想到枚举二分深度,然后用getd看一看它们的祖先是不是一样的,然后一样就往下,不一样就往上。
嗯,搞定。

这个的时间复杂度为 Θ(log2n)2
和方法一对比一下:
这里写图片描述
嗯,有趣的函数。

为什么我会说这么多嗯?
嗯,不再说嗯了。
嗯,上代码。

int YiDianErYeBuBaoLiDeErFen(int u,int v) 
{ 
    if(dep[u]<dep[v]) t=u,u=v,v=t; 
    u=getd(u,dep[v]);

    if(u==v) return u; 
    int l=1,r=dep[v]; 
    while(l<=r) 
    { 
        int mid=(l+r)>>1; 
        if(getd(u,mid)==getd(v,mid)) l=mid+1; 
        else r=mid-1; 
    } 
    return f[getd(u,l)][0]; 
} 




方法三:正规倍增方法

也很简单,跟方法二基本思路差不多,可以直接上代码。

int lca(int u,int v) 
{ 
    if(dep[u]<dep[v]) t=u,u=v,v=t; 
    u=getd(u,dep[v]); 

    if(u==v) return u; 
    for(int i=LOG-1;i>=0;i--) 
        if(f[u][i]!=f[v][i]) 
            u=f[u][i],v=f[v][i]; 
    return cut[u]; 
} 

时间复杂度为 Θ(log2n)

嗯,讲完了。









一道裸题

【USACO MAR11银组】聚会地点

时间限制: 1 Sec 内存限制: 64 MB
提交: 97 解决: 98

题目描述

Bessie和Jon每天都要去他们所居住的小镇的某些地方游玩。有趣的是,他们居住的小镇是一个树的结构,也就意味是,小镇的每个地方之间有且仅有一条通路(不是指一条边,而是指一条通路),每个地方都会有且仅有一个父亲地点(除了小镇的城镇中心,它没有祖先)。

小镇共有N个地点(1 <= N <= 1,000),编号1~N。点1是镇的中心。

Bessie和Jon决定每天都要在游玩后见面,他们见面的地点总是在他们游玩的两个地方之间的那条通路中,离城镇中心最近的地方,下面给出他们的旅行日程,你需要帮他们每天的见面地点。
你可以理解为城镇中心就是成为在这个树结构上的根。

                          地点        它的父亲地点
         [1]           ---------    ----------------
        / | \              1              ---(城镇中心没有父亲)
       /  |  \             2               1 
     [2] [3] [6]           3               1
     /        | \          4               2
    /         |  \         5               8
  [4]        [8]  [9]      6               1
            /   \          7               8
           /     \         8               6
         [5]     [7]       9               6

以下为他们某次见面的安排:

   |  Bessie   |    Jon    |   Meeting Place  | 
   | --------- |  -------- |  --------------- | 
   |     2     |     7     |        1         | 
   |     4     |     2     |        2         | 
   |     1     |     1     |        1         | 
   |     7     |     5     |        8         | 
   |     9     |     5     |        6         | 
输入

第1行:两个数N,M代表一共有N个地方,B和J已经进行了M次见面
第2..N-1行,每行一个数X,代表第i个地点的父亲为X
再接下来M行,每行两个数,分别代表B和J当天准备去游玩的地方

1<=N<=1000,1<=M<=1000

输出

一共M行,每行一个数代表B和J当天见面的地方。

样例输入

9 6
1
1
2
8
1
8
6
6
2 7
4 2
3 3
4 1
7 5
9 5

样例输出

1
2
3
1
8
6






嗯,裸题一个,上代码。


详见代码


数据很小,暴力也可以过。

#include<iostream> 
#include<cstdio> 
#include<vector> 
using namespace std; 

const int MAXN=1024; 
const int LOG=12; 

void Read(int &p) 
{ 
    p=0; 
    char c=getchar(); 
    while(c<'0'||c>'9') c=getchar(); 
    while(c>='0'&&c<='9') 
        p=p*10+c-'0',c=getchar(); 
} 

int dep[MAXN],cut[MAXN],f[MAXN][LOG],n,m,u,v,t; 
vector <int> down[MAXN]; 

void dfs(int i) 
{ 
    if(i!=1) dep[i]=dep[cut[i]]+1; 
    for(unsigned int j=0;j<down[i].size();j++) 
    dfs(down[i][j]); 
} 

int getk(int x,int k) 
{ 
    for(int i=0;i<LOG;i++) 
        if(k&(1<<i)) x=f[x][i]; 
    return x; 
} 

int getd(int x,int d) 
{ 
    return getk(x,dep[x]-d); 
} 

int lca(int u,int v) 
{ 
    if(dep[u]<dep[v]) t=u,u=v,v=t; 
    u=getd(u,dep[v]); 

    if(u==v) return u; 
    for(int i=LOG-1;i>=0;i--) 
        if(f[u][i]!=f[v][i]) 
            u=f[u][i],v=f[v][i]; 
    return cut[u]; 
} 

int BAOLI(int u,int v) 
{ 
    if(dep[u]<dep[v]) t=u,u=v,v=t; 
    u=getd(u,dep[v]); 

    if(u==v) return u; 
    int l=1,r=dep[v]; 
    while(l<=r) 
    { 
        int mid=(l+r)/2; 
        if(getd(u,mid)==getd(v,mid)) l=mid+1; 
        else r=mid-1; 
    } 
    return cut[getd(u,l)]; 
} 

int main() 
{ 
    Read(n); Read(m); 
    for(int i=2;i<=n;i++)  
        Read(cut[i]),down[cut[i]].push_back(i); 

    for(int i=1;i<=n;i++) f[i][0]=cut[i]; 
    for(int j=1;j<LOG;j++) 
        for(int i=1;i<=n;i++) 
            f[i][j]=f[f[i][j-1]][j-1]; 
    dfs(1);  

    for(int i=1;i<=m;i++) 
        Read(u),Read(v), 
        printf("%d\n",BAOLI(u,v)); 
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值