樹上倍增求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]$
- 讓$x$向上跳,跳到與$y$深度相同位置
- 讓$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發展城市——RMQ求LCA+樹鏈的交
題目描述 眾所周知,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