1. 程式人生 > >樹上倍增求LCA及例題

樹上倍增求LCA及例題

先瞎扯幾句

樹上倍增的經典應用是求兩個節點的LCA

當然它的作用不僅限於求LCA,還可以維護節點的很多資訊

求LCA的方法除了倍增之外,還有樹鏈剖分、離線tarjan ,這兩種日後再講(眾人:其實是你不會吧:unamused:。。。

思想

樹上倍增嘛,顧名思義就是倍增

相信倍增大家都不預設,著名的rmq問題的$O(n*logn)$的解法就是利用倍增實現的

在樹上倍增中,我們用

$f[j][i]$表示第$j$號節點,跳了$2^j$步所能到達的節點

$deep[i]$表示$i$號節點的深度

然後用這兩個陣列瞎搞搞就能整出LCA來啦

眾人::wrench:  :hammer: :hocho:

實現

deep&&f[i][0]

首先,$f[i][0]$(也就是一個節點的上面的節點)容易求得,只要對整棵樹進行一邊dfs就好,在dfs的時候我們順便可以求出$deep$陣列

for(int i=head[now];i!=-1;i=edge[i].nxt)
        if(!deep[edge[i].v])
            deep[edge[i].v]=deep[now]+1,f[edge[i].v][0]=now,dfs(edge[i].v);

這段程式碼應該不難理解

f[j][i]

那麼我們怎麼維護$f$陣列呢?

不難得到$f[j][i]=f[f[j][i-1]][i-1]$ 眾人:難!

其實真的不難,一張圖就可以解釋明白啦

這句話的意思其實是說,一個節點跳$2^j$所能到達的節點實際上是跳$2^{i-1}$所能到達的節點再往上跳$2^{j-1}$步

注意$2^i=2^{i-1}+2^{i-1}$

程式碼:

for(int i=1;i<=19;i++)    
    for(int j=1;j<=n;j++)
        f[j][i]=f[f[j][i-1]][i-1];

LCA

接下來要進入最核心的部分啦,

我們如何用$deep$和$f$亂搞搞出$x$和$y$的LCA呢?

按照書上倍增演算法的介紹

我們求LCA需要分為兩步

設$deep[x]>deep[y]$

  1. 讓$x$向上跳,跳到與$y$深度相同位置
  2. 讓$x$和$y$同時向上跳,跳到祖先相同位置

根據二進位制分解什麼亂七八糟的,這麼做一定是對的,其實這個挺顯然的,yy一下就好了吧。。。

第一步

if(deep[x]<deep[y])    swap(x,y);
    for(int i=19;i>=0;i--)
        if(deep[f[x][i]]>=deep[y])
            x=f[x][i];

首先處理一下$x$和$y$的深度,保證$deep[x]>deep[y]$

然後儘量讓$x$向上跳就好啦,注意這裡是可以取到等號的

注意這裡可能會出現一種特殊情況

這個時候他們的最近公共祖先就是$y$

if(x==y)    return x;

 第二步

同時向上跳,直到祖先相同為止

那麼此時他們再向上跳一步所能到達的節點就是LCA啦

for(int i=19;i>=0;i--)
    if(f[x][i]!=f[y][i])
        x=f[x][i],y=f[y][i];
return    f[x][0];

怎麼樣?

是不是很簡單?

完整程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1000010;
inline void read(int &n)
{
    char c=getchar();bool flag=0;n=0;
    while(c<'0'||c>'9')    c=='-'?flag=1,c=getchar():c=getchar();
    while(c>='0'&&c<='9')    n=n*10+c-48,c=getchar();flag==1?n=-n:n=n;
}
struct node
{
    int v,nxt;
}edge[MAXN];
int head[MAXN];
int num=1;
inline void add_edge(int x,int y)
{
    edge[num].v=y;
    edge[num].nxt=head[x];
    head[x]=num++;
}
int f[MAXN][21];
int deep[MAXN];
int n,m,root;
void dfs(int now)
{
    for(int i=head[now];i!=-1;i=edge[i].nxt)
        if(!deep[edge[i].v])
            deep[edge[i].v]=deep[now]+1,f[edge[i].v][0]=now,dfs(edge[i].v);
}
void PRE()
{
    for(int i=1;i<=19;i++)    
        for(int j=1;j<=n;j++)
            f[j][i]=f[f[j][i-1]][i-1];
} 
int LCA(int x,int y)
{
    if(deep[x]<deep[y])    swap(x,y);
    for(int i=19;i>=0;i--)
        if(deep[f[x][i]]>=deep[y])
            x=f[x][i];
    if(x==y)    return x;
    for(int i=19;i>=0;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return    f[x][0];
}
int main()
{
    
    memset(head,-1,sizeof(head));
    read(n);read(m);read(root);
    for(int i=1;i<=n-1;i++)
    {
        int x,y;read(x);read(y);
        add_edge(x,y);
        add_edge(y,x);
    }
    deep[root]=1;
    dfs(root);
    PRE();
    for(int i=1;i<=m;i++)
    {
        int x,y;
        read(x);read(y);
        printf("%d\n",LCA(x,y));
    }
    return 0;
} 

例題

都是些入門難度的題目

http://www.cnblogs.com/zwfymqz/p/6832524.html

http://www.cnblogs.com/zwfymqz/p/7791527.html

http://www.cnblogs.com/zwfymqz/p/7791617.html

http://www.cnblogs.com/zwfymqz/p/7791517.html

相關推薦

樹上倍增LCA例題

先瞎扯幾句 樹上倍增的經典應用是求兩個節點的LCA 當然它的作用不僅限於求LCA,還可以維護節點的很多資訊 求LCA的方法除了倍增之外,還有樹鏈剖分、離線tarjan ,這兩種日後再講(眾人:其實是你不會吧:unamused:。。。) 思想 樹上倍增嘛,顧名思義就是倍增 相信倍增大家都不預設,

樹上倍增LCA

oid void for print names != ostream tmp iostream 大概思想就是,節點$i$的第$2^{j}$個父節點是他第$2^{j-1}$個父親的第$2^{j-1}$個父親 然後可以$O(nlogn)$時間內解決…&hell

【模板】樹上倍增LCA

參考題目:Tree 題目描述 給出一棵帶有邊權的樹,問兩點之間的距離。 輸入格式 第一行兩個整數 n 和 m ,分別表示點數和詢問數。 接下來 n-1 行,每行三個整數 x,y,z,表示 x 與 y 通過一條權為 z 的邊連線。 接下來 m 行,每行兩個整數 x

tarjan,樹剖,倍增lca

next 訪問 find int ext for pac using ins 1.tarjan求lca Tarjan(u)//marge和find為並查集合並函數和查找函數 { for each(u,v) //訪問所有u子節點v    {  

倍增LCA

etc ++ sin bre i++ printf floor break truct #include<bits/stdc++.h> using namespace std; struct node{ int to,next,w; }e[100000

洛谷3379 倍增LCA

#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #define ll long long using namespace std; const ll maxn=

圖論——倍增LCA

LCA,最近公共祖先。 這是在樹上的演算法,但是為什麼我們把它歸為圖論呢? 因為它對圖論太重要了,其實,樹也是圖,是任意二節點只有一條路徑的圖。 我們來看一下LCA的栗子: 這就是LCA,很好理解吧! 那問題來了,怎麼實現求兩點的LCA呢? 其實很簡單,用暴力法就可以了。先用樹的DFS遍歷求出樹的深度,在

CoderForces Round526F Max Mex(倍增LCA+線段樹路徑合併)

Max Mex time limit per test 3 seconds memory limit per test 256 megabytes input standard input

樹上倍增LCA

#include<set> #include<map> #include<cmath> #include<queue> #include<stack> #include<cstdio> #include

倍增LCA模板

1.引入 2.思路 這道題目是倍增求LCA的模板題。 首先,大家都知道LCA的定義吧?(兩個節點的公共父節點)如果我們求兩個點的LCA的使用暴力求解(DFS找出要求點的深度,一個一個往上跳,一次一次查詢),在卡時間的競賽中是肯定會炸掉的。那麼,我們就使用另一種方法,

12.16+樹上倍增LCA

樹上倍增求LCA的步驟: 1.預處理:節點的深度d、到根節點的距離dist、該點向上走2^k步能夠到達的點:f陣列。 2.lca: ①.將兩個節點調整到同一個深度,只調整深的那個即可。 ②.如果①結束後,這兩個點重合,說明該點就是所求的點。 ③.否則,從大往小開始試跳躍的步數

倍增LCA——在線

處理 nod logs 記錄 數組 ide 預處理 earch 就是 預處理 完整代碼 推薦 B站視頻講解 首先我們考慮暴力做法:   分別從兩個點開始一層一層地向上跳,直到跳到一樣的點,這個點就是他倆的LCA了。 這個做法實在太暴力了,不可取,不可取. .

如何用倍增LCA——洛谷[P3379]題解

.org get .net ++ == main pri oid can 什麽是LCA? LCA就是最近公共祖先的縮寫,就是假如我們有下面的一個樹。那麽這個樹上的10號結點與7號結點的LCA就是2號結點 暴力的思路 在講正解之前我們先來講講如何用暴力來解決這個問題。因為倍

BZOJ1906樹上的螞蟻&BZOJ3700發展城市——RMQLCA+樹鏈的交

題目描述  眾所周知,Hzwer學長是一名高富帥,他打算投入巨資發展一些小城市。 Hzwer打算在城市中開N個賓館,由於Hzwer非常壕,所以賓館必須建在空中,但是這樣就必須建立賓館之間的連線通道。機智的Hzwer在賓館中修建了N-1條隧道,也就是說,賓館和隧道形成了一個樹形結構。

tarjan點雙+樹上倍增/圓方樹+並查集--business

對我沒打錯名字,就是 b u s i

hdu2586 /// tarjan離線樹上兩點的LCA

oid csdn log span name pre amp hdu2586 edge 題目大意: 詢問一棵樹裏 u 到 v 的距離 可由 dis[ u到根 ] + dis[ v到根 ] - 2*dis[ lca(u,v) ] 得到 https://blog.csdn.ne

LCA演算法 線上樹上倍增模板

測試資料 1 10 1 1 2 2 1 4 4 2 3 3 2 5 6 3 7 1 3 8 2 5 6 3 6 9 2 4 10 4 9 10 終於造了什麼事樹上倍增了下午考pat。。。哭卿卿 程式碼理解來自 自己又加了備註。。。。 建議模擬一下 https://blog.csdn

咳咳,用樹狀陣列逆序對例題

關於樹狀陣列,相信大家都已經比較熟悉了。。。 那麼,我們就先來砍一刀例題(嘻嘻) 輸入 給出n以及n個數,求這其中的逆序對個數 PS:逆序對,就是序列中ai>aj且i<j的有序對。 輸入: 6 5 4 2 6 3 1 輸出: 11 n<=5*10^5 ai&

HDU 2586 How far away ?(LCA Tarjan/樹上倍增

題目:問任意兩個點之間的最短路徑長。 如果用Tarjan做的話,那麼 用LCA算出最近公共祖先lca,長度就是dis[u]+dis[v]-2*dis[lca] #include<iostream> #include<cstdio> #incl

倍增 Tarjan LCA