fhq treap(無旋treap) 學習筆記

2021-08-27 21:14:04 字數 4362 閱讀 7090

首先最好要會寫treap(也先了解一下笛卡爾樹是什麼。。。)

fhq treap和treap同樣有乙個隨機分配的rnd值,用於平衡,但fhq treap不需要旋轉操作來維持平衡,因為有兩個神奇的操作merge和split

在兩種操作之前,要明確的一點是fhq treap依靠rnd值來維護平衡,把每個點按照小根堆的方式放置,即根rnd小於子rnd,並且在任何時候都要保證有二叉搜尋樹的性質,即左子樹key <= 根節點key <= 右子樹key

其實我們可以把相等的點縮在一起看做根,這樣就滿足了左子樹《根《右子樹了,就算有一些和根相同的點跑到右子樹上了,由於我們在各種操作的過程中是不斷往右子樹上跑的,所以這些點還是會考慮到

下面兩個函式是fhq treap的核心,理解好了其他操作就十分簡單

注意兩個操作都要update(now)

遞迴的過程,在執行之前需要保證x中所有點的key值(要儲存的值,不是隨機值)都小於y的(簡稱為x < y),這是二叉搜尋樹的性質,而如何保證這個性質先不用著急,在插入操作中,這個性質自然實現了。最後要返回當前根節點

int merge(int x, int y) 

tr[y].ls = merge(x, tr[y].ls);//把x接在y的左子樹上

update(y);

return y;

}

權值小於等於k的分到左樹(x),大於的分到右樹(y)。

注意引數中的x,y是「x樹中等待和別的點相接的點,y樹種等待和別的點相接的點」這個意思

等待這個詞很微妙,下面通過乙個具體的情形解釋一下

首先x,y均為0,然後判斷一下,確定目前x或y的樹根

若目前now的值小於等於k,則now應該放在x樹上,放在x樹的**呢?就放在函式目前的引數上,即x「等待相接的位置」,並且x左子樹均比k小,不需要改動。

然後現在整棵樹變為兩部分 now及其左子樹,now的右子樹,顯然在上述情形下,下一次要劃分的樹就是now的右子樹,並且按照二叉搜尋樹的性質應當把劃分下來的,值仍然小於等於k的子樹接在x的右子樹上,所以此時now的右子樹就是「x等待相接的位置」

另一種情況同理

並且不用擔心這樣分會錯(比如在第一種情況中now的右節點還是原來的,但是在不斷遞迴的過程中now的右節點要麼被更新,要麼由if(!now) x = y = 0;變為0)

void split(int now, int k, int &x, int &y)  

if(tr[now].val <= k)

x = now, split(tr[now].rs, k, tr[now].rs, y);

else

y = now, split(tr[now].ls, k, x, tr[now].ls);

update(now); //那些now = 0的點不能呼叫update,因為會使得本來siz為0的點變為1(看update**,無論如何也會加1)

}

解決區間問題的時候要按size分

這時k為size 若now樹的左子樹有》=k個節點,那麼就從其左子樹中找k個

否則,從其右子樹中找k-tree[lson].size-1個(左邊貢獻了足夠多的點) 減去的1是根節點(有點像treap求第k大)

void split(int now, int k, int &x, int &y) 

}

操作時注意實時維護root具體是哪個點
void new_node(int val)
為了保證"x < y"這個性質,只好按val拆分,然後在合併x和點new的時候雖然x中有和new值相同的,但是問題不大,

split(root, val, x, y);

root = merge(merge(x, new_node(val)), y);

注意合併的時候要反著拆分的順序操作,仍然是為了保證"x < y"

下面的操作成功分出了乙個c,而c的根的值一定為val,這是因為前面劃分除出了乙個a,a只有兩種點,值為val的點和值<=val的點,而c又是從a中分出來的

split(root, val, a, b);

split(a, val-1, a, c);

c = merge(tree[c].lson, tree[c].rson);

root = merge(merge(a,c), b);//a,c最後拆,最先合

把整棵樹以 val-1 split 分為x和y 然後答案就是x.size+1,當然前提是這個值存在

和treap一樣

int kth(int now, int k)  else  else 

} }}

按val-1劃分,左樹裡面找最大的,即在左樹裡面找rank為左樹size的即可

按val劃分,右樹rank為1的就是

構造一棵treap,使得對treap中序遍歷就能得到初始區間

其實應該模仿笛卡爾樹,線性建樹的,但是我不會,而且大多數題沒卡到那個地步

一般就是那種,詢問少,n很大,nlogn過不去,但是qlogn能過去的題,就得模仿笛卡爾樹建樹了,開個單調棧什麼的,我目前還不會寫。。。

int build(int l, int r)
劃分兩次,把樹分出來乙個l~r的,然後進行翻轉操作,具體來說就是把乙個點的左右兒子交換,對於翻轉區間內每乙個點都進行這麼個操作,最後這個區間就被整體翻轉了。這裡有懶標記的思想,打上標記把樹合併就行了

void reverse(int l, int r)
輸出:中序遍歷

這裡有懶標記的思想,所以輸出時還要下傳

void dfs(int now)
普通平衡樹:

#include #include #include #include using namespace std;

#define debug(x) cerr << #x << "=" << x << endl;

const int maxn = 100000 + 10;

typedef long long ll;

int n,val,opt,a[maxn],root,x,y,z,tot;

struct fhqtreaptr[maxn];

int newnode(int x)

void update(int now)

int merge(int x, int y)

tr[y].ls = merge(x, tr[y].ls);

update(y);

return y;

}void split(int now, int k, int &x, int &y)

if(tr[now].val <= k)

x = now, split(tr[now].rs, k, tr[now].rs, y);

else

y = now, split(tr[now].ls, k, x, tr[now].ls);

update(now);

}int kth(int now, int k) else else

} }}int main() else if(opt == 2) else if(opt == 3) else if(opt == 4) else if(opt == 5) else

}return 0;

}

文藝平衡樹:

#include #include #include #include using namespace std;

const int maxn = 100000 + 10;

struct trnodetr[maxn];

int n,m,tot,root;

void update(int now)

void down(int now)

int new_node(int val)

int build(int l, int r)

int merge(int x, int y) else

}void split(int now, int k, int &x, int &y)

}void rever(int l, int r)

void dfs(int now)

int main()

dfs(root);

return 0;

}

Fhq Treap無旋Treap練習

洛谷p3369 模板 普通平衡樹 split採用按權 author revolia submit includeusing namespace std typedef long long ll const int maxn 1e6 5 int l maxn r maxn val maxn size ...

演算法學習 FHQ Treap (無旋Treap)

fhq treap和普通的treap都是乙個二叉搜尋堆,其同時滿足二叉樹的性質 左子樹的權值小於等於當前節點權值,右子樹權值大於當前節點權值 和堆的性質 對於小根堆,當前節點的優先順序是堆中最小的 fhq treap與一般的treap的不同之處主要在於 不用旋轉,用split和merge來為維護堆的...

演算法學習 Fhq Treap(無旋Treap)

treap 大名鼎鼎的隨機二叉查詢樹,以優異的效能和簡單的實現在oier們中廣泛流傳。這篇blog介紹一種不需要旋轉操作來維護的treap,即無旋treap,也稱fhq treap。它的巧妙之處在於只需要分離和合併兩種基本操作,就能實現任意的平衡樹常用修改操作。而不需要旋轉的特性也使編寫 時不需要考...