Hdu 2586 學習 LCA 的 ST 表做法

2021-08-15 13:18:25 字數 3362 閱讀 2447

還是hdu 2586 , 求一棵樹中任意兩點間距離,學習 st 表。

st 表是求區間最值的一種預處理 o(nlogn) , 查詢 o(1) 的動態規劃。

例如長度為8的序列,12 5 3 20 9 7 4 1 

用 dp[i][j] 代表從第 i 個開始(包括第 i 個)往後走 2^j 步,所涵蓋的區間的最值。拿最小值為例,dp[1][0] = 12 , 因為走 2^0 步還是自己(包括自己是一步) ,dp[1][1] = 5 , 求的是 區間 [ 1 , 2 ] 的最小值,dp[1][2] = 3 ,求的是 [ 1 , 4 ] 的最小值,dp[1][3] = 1 , 求的是   [ 1 , 8 ]的最小值。

這裡有區間的二分,應該說是 1 推導 2 , 2 推導 4 , 4 推導 8 , 8 推導 16。

區間 [ 1 , 8 ] 的最小值就是 [ 1 , 4 ] , [ 5 , 8 ] 兩個區間最小值中更小的那個      , 子區間長度 4

區間 [ 1 , 4 ] 的最小值就是 [ 1 , 2 ] , [ 3 , 4 ] 兩個區間最小值中更小的那個      , 子區間長度 2

區間 [ 1 , 2 ] 的最小值就是 [ 1 , 1 ] , [ 2 , 2 ] 兩個區間最小值中更小的那個      , 子區間長度 1

所以要求 [ 1 , 8 ] 區間的最小值就可以先求子區間的最值,最後推導而出。

先看核心**:

for( int i = 1 ; i <= len ; ++i )

dp[i][0] = a[i] ;

for( int j = 1 ; (1初始狀態就是走 2^0 步,就是自己

第一層 for 迴圈,決定子區間長度的,長度為 7 ,就會有 4 , 2  , 1 長度的子區間,長度為 15 是 8 , 4 , 2 , 1 長度的子區間,長度為 16 , 就會有16 , 8 , 4 , 2 , 1 長度的子區間。

第二層 for 迴圈,決定在區間限制之內,可以求最值的起點,例如 10 ,最大子區間長度是 8 , 可以起步找的位置只能是  1 , 3 ,然後求 [ 1 , 10 ] 的最值,就可以求 [ 1 , 8 ] 和 [ 3 , 10 ] 的最值,這樣一定會覆蓋,不會漏,因為 2^(j-1) + 2^(j-1) 大於等於整個區間的長度,如果小於了, 求出來的 j 值可以更大,就不是剛才的  j 了。或者說 i + 2 ^ (j-1) 一定比整個區間的一半要更大。

整體兩層 for 迴圈就是 子區間長度從  1 , 2 , 4 , 8 逐漸遞推,更長的 2^j 長度區間由上一次的 2 ^ (j-1) 長度的區間推導而來。

查詢,就是從區間左邊往後走 2 ^ (j-1) 的距離,從區間右邊往前走  2 ^ (j-1) 的距離,覆蓋整個要查詢的區間。

今天學習了 lca 和 rmq 的關係,挺有趣的。遍歷整棵樹,得出經過每個點的序列,查詢兩個點的lca,就在序列中找到兩個點第一次出現的位置,區間之內深度最小的那個點就是 lca 。

這個很好理解的,深搜的順序要回溯的嘛,回溯的過程中,如果訪問到其他分支,深度就會增大,在 lca 的地方深度最小

拿這兩幅圖好好看,我覺得就可以了。我感覺和 tarjan  演算法有相似之處,都是根據 「當前」 的集合頂點(已經搜尋完的點,所以最頂上的根是最後搜尋完的),在回溯的過程中,從** 「可以」 開始 , 或者 「分叉」, 找到另乙個點的地方,就是lca 。

tarjan 演算法是離線的,就是只處理需要查的,批量查完,有些點之間的 lca 可以不管 ;

根據區間最值,例如 st 表,就是先得出所有點訪問的順序(點可重複),然後用二分合併(有點像倍增)的方法,求出所有點之間的 lca 。lca 就是在『當前』訪問序列上深度最低的點,這和 lca 定義時吻合的。

hdu  2586  求兩點間距離:

#include using namespace std ;

int n , q ;

int head[40001] , k ;

int dis[40001] ;

int first[40001] ; // 記錄這個點第一次出現的位置

int ver[40001<<1] , dep[40001<<1] , top ; // ver 記錄 2*n-1 序列的點 , r 記錄這些點的位置

int dp[40001<<1][18] ; // dp 求 2*n-1 序列區間最值 , dp[i][j] 代表從 i 開始走 j 步的最值

struct node e[40001<<1|1] ;

void add( int u , int v , int cost )

void dfs( int u , int depth ) // ver 陣列和 dep 陣列兩次記錄,就是因為有回溯這一過程,經過點的序列可重複

}void get_dp()

int main()

dis[1] = 1 , top = 0 ;

dfs( 1 , 1 ) ;

get_dp() ;

while( q-- )

} return 0 ;

}

還有一道差不多的題目 scu 3365 也是 lca 的基礎題,重在理解 lca 和 rmq 的關係,多畫幾個圖,就會明白的。

#include using namespace std ;

int n , q ;

int head[1005] , k ;

int dis[1005] ;

int first[1005] ; // 記錄這個點第一次出現的位置

int ver[1005<<1] , dep[1005<<1] , top ; // ver 記錄 2*n-1 序列的點 , r 記錄這些點的位置

int dp[1005<<1][11] ; // dp 求 2*n-1 序列區間最值 , dp[i][j] 代表從 i 開始走 j 步的最值

struct node e[1005<<1|1] ;

void add( int u , int v , int cost )

void dfs( int u , int depth )

}void get_dp( int len )

int main()

dis[1] = 1 , top = 0 ;

dfs( 1 , 1 ) ;

get_dp( 2*n-1 ) ;

while( q-- )

}return 0 ;

}

HDU2586 LCA樹上倍增模板)

題意 給你一棵樹樹上任意兩節點有且僅有一條路徑可以到達,問求任意兩節點間的距離 題解 lca樹上倍增 o include include include include include include includeusing namespace std define clr a,b memset ...

ST求LCA的模板

預處理的時間複雜度是 o nlog2n 查詢時間是 o 1 include include include using namespace std const int maxn 10010 int rmq 2 maxn rmq陣列,就是尤拉序列對應的深度序列 struct st for int j ...

hdu2586 LCA應用 求樹的任意兩節點的距離

題意 給出一棵樹,求兩節點的的距離 解題思路 直接bfs超時,所以要使用lca 離線tarjan演算法 例如求x,y節點的距離,那麼先求出x,y的lca是u節點,那麼結果就是dis x dis y 2 dis u include include include include include inc...