1. 程式人生 > >故事篇之 LCA與倍增 (二)

故事篇之 LCA與倍增 (二)

規模 .html 快的 我們 tps else 通過 收益 mar

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

關於它的故事,想來自己功力不及,大家可以參考下面的鏈接,絕對精彩

白話倍增

看完之後,你可能不禁為 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 的 樹鏈剖分

另外可以去洛谷刷一些題來豐富自己的精神生活

所以 本篇就此結束了

本人才疏學淺,如有學術上的錯誤還望大家不吝指出

本文多有參考,若有侵權,小人當低頭認錯。

故事篇之 LCA與倍增 (二)