首先\(\mathrm\)問題指的是求解樹上兩點的最近公共祖先,\(\mathrm\)問題指的是求解數列區間最值。
\(\mathrm\)問題轉\(\mathrm\)問題應該是人盡皆知了,我們可以先跑出樹的\(\mathrm\)序,使用每次進入或回到節點都記錄一次的那種\(\mathrm\)序,那麼只需記錄每個節點第一次出現位置就可以查詢了。
具體來說,我們找到兩個點分別在\(\mathrm\)序中第一次出現的位置,那麼容易得知它們的\(\mathrm\)就是這段序列區間內深度最淺的點,那麼就把問題轉換成了尋找區間最小值。
這個可能稍微高階一點。首先我們要知道笛卡爾樹,就是把\(n\)個二元組\((x,y)\)建成一棵樹,使得\(x\)這一維是二叉搜尋樹,\(y\)這一維是堆,當然大根堆小根堆都可。可想而知,\(\mathrm\)就是第二維隨機的笛卡爾樹。
那麼根據笛卡爾樹的定義可知,乙個區間的最大\(/\)最小值就是這兩點在笛卡爾樹上的\(\mathrm\),因為深度越淺的節點優先順序越高,並且它們的\(\mathrm\)一定被包括在序列區間內,這樣就把問題轉換成求\(\mathrm\)了。
首先\(\mathrm\)轉\(\mathrm\)不用多說,\(\mathrm\)是\(\mathcal(n)\)的,那麼我們需要考慮一下如何根據序列構造笛卡爾樹。
\(\mathrm\)的方法就是用資料結構找區間最值,遞迴建樹,不過這樣你都會找區間最值了,那還有什麼好轉的呢?
其實笛卡爾樹有\(\mathcal(n)\)的構建方法,只需要每次維護字首笛卡爾樹右鏈上的節點就可以了,小根堆笛卡爾樹參考**如下:
for (int i = 1 , k; i <= n; i++)
這個應該不用多說,網路上資料很多,我們對比一下即可。
\(\mathrm\)演算法
預處理複雜度
詢問複雜度
樹鏈剖分
\(\mathcal(n)\)
\(\mathcal(\log_2 n)\)
樹上倍增
\(\mathcal(n\log _ 2n)\)
\(\mathcal(\log_2 n)\)
離線\(\mathrm\)
\(-\)
\(\mathcal(n\alpha(n)+q)\)
\(\mathrm\)序轉化的\(\mathrm\)演算法
\(\mathcal(n\log_2 n)\)
\(\mathcal(1)\)
\(\mathrm\)演算法
預處理複雜度
詢問複雜度
線段樹\(\mathcal(n)\)
\(\mathcal(\log_2 n)\)
\(\mathrm\)演算法
\(\mathcal(n\log _ 2n)\)
\(\mathcal(1)\)
\(\mathrm\)演算法
\(\mathcal(n\log_2 \log_2 n)\)
\(\mathcal(1)\)
然而,毒瘤們肯定不會滿足於上面這些簡單經典演算法的時間複雜度。
首先對於\(\mathrm\)問題,我們可以跑\(\mathrm\)序\(\mathcal(n)\)轉化為\(\mathrm\)問題,而我們注意到\(\mathrm\)序中相鄰兩個元素差的絕對值不超過\(1\),我們稱之為\(\mathrm\),可以利用這個性質優化演算法。
當然對於一般的\(\mathrm\),可以多一步笛卡爾樹的轉化,再跑\(\mathrm\)序,同樣可以轉化為\(\mathrm\)問題。
考慮把序列分成\(x\)塊,每塊處理最值,然後對塊之間處理\(\mathrm\)。當\(x\)取\(\log_2n\)時,預處理時間複雜度不大於\(o(n)\)。
然後我們預處理每塊的字首字尾最值,這樣就可以\(\mathcal(1)\)回答跨越兩個塊的詢問了。
那麼我們現在要做的就是想辦法快速處理同乙個塊內的詢問。首先我們注意到對於\(\pm 1\)序列相同的數列,其\(\mathrm\)問題的解都相同,現在我們只要把序列分成大小為\(\frac\)的塊,那麼本質不同的塊就只有\(n^\)種。對於每乙個本質不同的塊,直接\(\log^2n\)處理答案,那麼就可以得到乙個\(\mathcal(\sqrt n\log ^2 n)\)時間預處理,\(\mathcal(1)\)回答的演算法。
缺點在於,上述演算法實現難度太大,轉化太多,實用性不大。
我們有更簡單的解決方案,我們可以暴力處理塊內詢問,時間複雜度最差為\(\mathrm(n+q\log_2n)\)。但是,由於絕大多數詢問都是\(\mathcal(1)\)回答的,所以常數極小。並且,在資料隨機的情況下,可以直接認為其回答一次詢問的期望複雜度為\(\mathcal(1)\)。由於我們可以微調塊大小,所以此演算法幾乎不可卡滿。更大的好處是,**量減小了,甚至不需要笛卡爾樹的轉化。
const int n = 2e7 + 2 , logn = 26;
int n,m,s,size,t,a[n],log[n/24],pre[n],suf[n],f[n/24][logn];
#define lborder(x) ( (x-1) * size + 1 )
#define rborder(x) ( x == t ? n : size * x )
#define belong(x) ( ( x % size == 0 ) ? ( x / size ) : ( x / size + 1 ) )
inline void setblocks(void)
for (register int k = 1; (1《該演算法還可以使用根據巧妙的分塊大小優化,使其時間複雜度達到嚴格\(\mathcal((n+q)\sqrt)\)
神奇的是,我們還可以換一種思路:針對塊內詢問,我們狀壓以每個點為左端點開始的單調佇列,使用位運算技巧可以直接得到答案,時間複雜度嚴格\(o(n+q)\),由於博主沒有寫過,就不詳細講了。
RMQ問題與LCA問題
一 區間最小 最大查詢 range minimum maximum query rmq 問題 toj 2762 描述 已知長度為l 的數列a 詢問區間 l,r 中的最值。若詢問的次數較少,可以用線性的複雜度來查詢,但如果詢問的次數過多且l 過大,那麼複雜度就會很高。所以需要更快速的查詢方法。st 演...
演算法之LCA與RMQ問題
1 概述 lca least common ancestors 即最近公共祖先,是指這樣乙個問題 在有根樹中,找出某兩個結點u和v最近的公共祖先 另一種說法,離樹根最遠的公共祖先 rmq range minimum maximum query 即區間最值查詢,是指這樣乙個問題 對於長度為n的數列a,...
LCA問題的RMQ解法解析
lca問題是指最近公共祖先問題,rmq問題是只區間最小值問題,我們可以將lca問題轉化為rmq問題,然後利用rmq的解法來解決lca問題。有關rmq問題的詳解可以參考我的部落格,有關於rmq問題的詳解。本部落格重點講如何將lca問題轉化為rmq問題。當我們深度遍歷樹時,我們沒遇到乙個未訪問過的節點就...