LCA與RMQ的相互轉換

2021-05-26 20:52:40 字數 4548 閱讀 2323

一、最近公共祖先(leastcommon ancestors)

對於有根樹t的兩個結點u、v,最近公共祖先lca(t,u,v)表示乙個結點x,滿足x是u、v的祖先且x的深度盡可能大。另一種理解方式是把t理解為乙個無向無環圖,而lca(t,u,v)即u到v的最短路上深度最小的點。

這裡給出乙個lca的例子:

例一對於t=

v=e=

則有:lca(t,5,2)=1

lca(t,3,4)=3

lca(t,4,5)=3

二、rmq問題(range minimum query)

rmq問題是指:對於長度為n的數列a,回答若干詢問rmq(a,i,j)(i,j<=n),返回數列a中下標在[i,j]裡的最小值下標。這時乙個rmq問題的例子:

例二對數列:5,8,1,3,6,4,9,5,7有:

rmq(2,4)=3

rmq(6,9)=6

rmq問題與lca問題的關係緊密,可以相互轉換,相應的求解演算法也有異曲同工之妙。下面給出lca問題向rmq問題的轉化方法。

對樹進行深度優先遍歷,每當「進入」或回溯到某個結點時,將這個結點的深度存入陣列e最後一位。同時記錄結點i在陣列中第一次出現的位置(事實上就是進入結點i時記錄的位置),記做r[i]。如果結點e[i]的深度記做d[i],易見,這時求lca(t,u,v),就等價於求e[rmq(d,r[u],r [v])],(r[u]數列e[i]為:1,2,1,3,4,3,5,3,1

r[i]為:1,2,4,5,7

d[i]為:0,1,0,1,2,1,2,1,0

於是有:

lca(t,5,2) = e[rmq(d,r[2],r[5])] =e[rmq(d,2,7)] = e[3] = 1

lca(t,3,4) = e[rmq(d,r[3],r[4])] =e[rmq(d,4,5)] = e[4] = 3

lca(t,4,5) = e[rmq(d,r[4],r[5])] =e[rmq(d,5,7)] = e[6] = 3

易知,轉化後得到的數列長度為樹的結點數的兩倍加一,所以轉化後的rmq問題與lca問題的規模同次。

三、笛卡兒樹(cartesiantree)

笛卡兒樹是一種特殊的堆,它的重要應用之一是實現rmq問題向lca問題的轉化。笛卡兒樹根據乙個數列構造,其根結點是長為n的數列a中的最小值a[i] 的下標i,左右孩子分別是由數列a[1...i-1]和a[i+1...n]構造的笛卡兒樹。下面的內容裡,我們說笛卡兒樹某結點的值,指的是其儲存的下標在原陣列裡對應的值。

由於笛卡兒樹的這一特性,不難發現求原數列a的某一段最小值,相當於求這一段的左右兩端在笛卡兒樹上所對應結點的最近公共祖先。

這裡有乙個定理:

陣列a的cartesian樹記為c(a),則rmq(a,i,j)=lca(c(a),i,j).證明略。

現在急待解決的問題是笛卡兒樹的建立方法。笛卡兒樹不一定是完全二叉樹,所以與堆的操作有些不同。由於笛卡兒樹的特性,對其進行中序遍歷一定會得到原數列,也就是說,可以把笛卡兒樹看作是將原數列中的最小值「提公升」到最高處,再將左右兩部分的最小值分別「提公升」到次高處…,最終形成一棵二叉樹。所以我們由a[1]開始逐個將數列裡的數加入笛卡兒樹內,新加入的結點一定在樹的最右路徑的最右端(沒有右孩子)。當加入a[i]時我們在已有的笛卡兒樹上找到最右路徑上找到大於a[i]最小值,將它的父結點作為新結點的父結點,它則作為新結點的左孩子。若根結點大於a[i]則整個樹作為新結點的左孩子,若最右路徑上沒有大於a[i]的結點,則a[i]加入最右路徑的最右下端。

由於每個結點最多進入和退出最右路徑各一次,因此均攤時間複雜度為θ(n)。

四、rmq問題的稀疏表演算法(sparse table)

ne[rwa]告訴我,他通過分段表實現了一種簡便的一般rmq問題的o(n)-o(n^0.5)的演算法。相比st演算法,這個演算法在空間上似乎更優。以下是他本人對這種演算法的介紹:

先把待求rmq的陣列分成每段長度lenth=sqrt(n)的小段,用陣列a記錄每段的最小值,這樣先判斷i和j所在的塊x,y如果x=y,那麼rmq (l,i,j)=in_rmq(x,i,j)否則:rmq(l,i,j)=min就可以在很快的時間內求出rmq問題了。預處理的時間複雜度為o(n),每次rmq最壞情況的時間複雜度為o (n^0.5)。 

例如:l=則分成4段a=

要求rmq(l,3,15)

那麼in_rmq(l,3,4)=6,rmq(a,2,3)=1,in_rmq(l,13,15)=7

所以陣列裡最小的元素為1,下標為7。

五、lca問題的tarjan離線演算法

tarjan和lca轉化為rmq的**(對應的題目是boj的p1278):

最近公共祖先

submit: 326   accepted:111

time limit: 1000ms  memory limit:65536k

description

給定一棵樹,我們定義樹中兩個節點a、b的最近公共祖先c為:

1、c既在a到根的路徑上,又在b到根的路徑上。

2、c離根的距離最遠。

如下圖:

節點4、5的最近公共祖先是8;節點9、6的最近公共祖先是8;節點7、11的最近公共祖先是4。

input

輸入包含多組測試資料。

首先第一行輸入乙個數t(t<=20),表示總共有t組測試資料。

接下來是每組測試資料,第一行是乙個數n(n<=200),表示樹中一共有n個點。接下來n-1行,每行兩個數a、b,表示a是b的父親。

然後是乙個數m(m<=1000),表示一共有m個查詢,接下來每個查詢有兩個數a、b,表示詢問a和b的最近公共祖先。

output

首先,輸出case #x:其中x代表是第x組資料(具體格式參照樣例)。

然後對每次查詢,輸出a和b的最近公共祖先。

sample input22

1 21

1 23

1 21 3

21 2

2 3sample output

case #1:

1case #2:11

source

humanjustic

#include

#include

#include

#include

#define size 10001

#define msize 16

using namespace std;

int tree[size] ; //tree[a]儲存著a的父親

int ord[2*size+1][2];//記錄著節點深度優先遍歷的順序和深度(根節點的深度為0)

int num; //ord陣列的元素個數

int n ; //節點的數量

int rnum[2*size+1][msize];

int first[size];

void change(int k , int de, int & nu)//把lca轉化為rmq

ord[nu][0]=k;

ord[nu][1]=de;

first[k]=nu;

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

if (tree[i]==k)

++nu;

change(i,de+1,nu);

ord[++nu][0]=k;

ord[nu][1]=de;

void rmq_predo( int k)

for ( int i=0 ; ifor ( int j=1; (1 << j) <=k ; j++ )

for (int i=1 ; i+(1 else rnum[i][j]=rnum[i+(1 << ( j-1 ))][j-1];

int get_ans(int i, int j)

int len=(int)(log((float)(j-i+1)/(log(2.0)))/(log(2.0)));

if (ord[rnum[i][len]][1] > ord[rnum[j-(1 << len)+1][len]][1])

return ord[rnum[j- (1 << len ) +1][len]][0];

else return ord[rnum[i][len]][0];

int get_root( int k) //找出樹的根

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

if (! tree[i]) return i;

int main ( )

int a,b;

scanf("%d", &n);

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

scanf("%d%d",&a,&b);

tree[b]=a;

num=0;

change(get_root(n),0,num);

rmq_predo(num);

int m;

scanf("%d",&m);

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

scanf("%d%d",&a,&b);

int x=first[a],y=first[b] ;

if ( x < y ) printf("%d",get_ans(x,y));

else printf("%d",get_ans(y,x));

LCA與RMQ演算法

初始化 init rmq max i j 中存的是重j開始的2 i個資料中的最大值,最小值類似,num中存有陣列的值 for i 1 to n max 0 i num i for i 1 to log n log 2 for j 1 to n 1 2 i max i j max max i 1 j ...

bstr t與CString相互轉換

bstr t與cstring相互轉換 bstr t bstr cstring strsql cstring bstr t bstr bstr t strsql bstr t cstring strsql lpcstr bstr bstr寬字串與cstring相互轉換 bstr bstr cstrin...

byte 與string相互轉換

c 中byte與string相互轉換及string與sql server中nvarchar的轉換問題 最近在寫專業實踐 資料庫加密,找出以前寫的md5 des程式,編成兩個動態鏈結庫md5.dll des.dll,在這把我遇到的問題分享下 1 byte與string的相互轉換 首先在c 中strin...