故事篇之 LCA与倍增 (二)

在此之前我们需要了解一下倍增

关于它的故事,想来自己功力不及,大家可以参考下面的链接,绝对精彩

白话倍增 

看完之后,你可能不禁为 2B小白兔 喝彩了吧

 晚上小册子丈量天涯海角,白天神机妙算蔑视群雄

用计算机术语来说就是 每次根据已经得到的信息 将考虑的规模扩大一倍,从而达到加速的目的

它有两个作用

一是 在变化规则相同的情况下加速状态转移

就例如 抛一枚硬币 要么就是正要么就是反 每次抛一次是如此 两次而是如此 那四次 八次呢?

二是 加速区间操作

这是用于LCA的关键 就例如 小白兔的故事, 走一步安守本分,那如果想要一步登天呢,只要通过小本本就可以!

这里你可很清楚的看到 其实这也是 一种以空间换取时间的方式,必然带来巨大的空间损耗,所以我们需要合理利用

至于 倍增的背景的了解,就此结束,因为lca还着我们呢

想要深入了解可以参考朱同学的   浅析倍增 

本文也从中收益


好了,让我们了解 倍增与lca这天造地设的一对,如何使出惊世骇俗的合体技 真LCA

从上文我们知道 最后求lca的过程中我们分别对两个点进行dfs,一个点搜完后保存他的祖宗,然后另一个点开始搜索与保存的祖宗进行一 一比较

仔细一想 这太麻烦了

咦 最后一步不是跟 最开始dfs疏通他们的关系的过程很像么,同是互相确认关系,只不过是目的不同罢了

看代码

1.dfs疏通关系

void dfs(int u,int fa) //u为当前节点 fa为它父亲节点
{
    d[u]=d[fa]+1;     //u比fa年轻一辈
    for(int i=head[u];i!=-1;i=e[i].next)  //找它的儿子
    {
        int v=e[i].v; 
        if(v!=fa)  // 不是爸爸,就是儿子
            dfs(v,u);//叫一声爸
    }
}

2.找lca

for(int i=head[u];i;i=edge[i].nex)
{
    int v=edge[i].to;
    if(d[v]<d[u]) //
    {
      for(int i=1;i<=ans;i++)
          if(v==mark[i]) {cout<<"找到了!!";return;} //看看 你现在的祖先 在对方的祖先中有没有 如果是就成功了 否则继续
      i=v;   //不然就继续找
    }
}

是不是很像?有没有觉得 第一步dfs疏通关系的过程 有成为 兔纸晚上小本本的潜质

没有错,只需我们通过预处理就可以实现我们自己的小本本 抟扶摇直上九万里咯!!

不过怎么实现呢?嗯, 既然是小本本就要开一个数组,例如从这里走几步可以到那

嗯 没错了 就是他 小本本[现在的位置][走多少步]=从这里走几步可以到那

然后通过预处理,就可以完美利用了

void dfs(int u,int fa) //u为当前节点 fa为它的父节点
{
    d[u]=d[fa]+1;     //承认爸爸
    p[u][0]=fa;       //表示的意思 就是小本本[当前位置][走几步] 其中 0表示为20=1,也就是u往上走一步,很显然就是他爸
    for(int i=1;(1<<i)<=d[u];i++)
        p[u][i]=p[p[u][i-1]][i-1];   //师傅带进门 那么自己领会啦, 预处理
    for(int i=head[u];i!=-1;i=e[i].next)  //正常继续。
    {
        int v=e[i].v;
        if(v!=fa)
            dfs(v,u);
    }
}                  

如此小本本做好了

看我们飞快的找lca了

int lca(int a,int b)              //非常标准的lca查找
{
    if(d[a]>d[b])
        swap(a,b);                 //保证a是在b结点上方,即a的深度小于b的深度
    for(int i=20;i>=0;i--)
        if(d[a]<=d[b]-(1<<i))
            b=p[b][i];             //先把b移到和a同一个深度
    if(a==b)
        return a;                 //特判,如果b上来和就和a一样了,那就可以直接返回答案了
    for(int i=20;i>=0;i--)        //从迈大步开始,万一就到了呢
    {
        if(p[a][i]==p[b][i])      // 这时先不要急 万一不是最近的呢
            continue;
        else
            a=p[a][i],b=p[b][i];           //A和B一起上移
    }
    return p[a][0];               //完美收工 
}

耶,成功了,可求出了LCA,我们能干什么呢?突然内心十分茫然。。

嗯,它的用处大得很,

根据树的特性

一是 lca路径即为树的两点最短路径

二是 利于区间操作

根据这些特点 你会见识到树上差分与树链剖分 都是大大的神器 达到O(n+m) 与O(logn)的优秀效率

如果有兴趣了解可以参考

顾z大大的 树上差分  

及 Chnihh 的 树链剖分

另外可以去洛谷刷一些题来丰富自己的精神生活

所以 本篇就此结束了

本人才疏学浅,如有学术上的错误还望大家不吝指出

本文多有参考,若有侵权,小人当低头认错。

 

posted @ 2018-11-03 16:33  归风寻址  阅读(205)  评论(0编辑  收藏  举报