首先最好要會寫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。它的巧妙之處在於只需要分離和合併兩種基本操作,就能實現任意的平衡樹常用修改操作。而不需要旋轉的特性也使編寫 時不需要考...