學習筆記 FHQ Treap

2022-04-29 20:09:11 字數 4497 閱讀 1665

fhq treap (%%%發明者範浩強年年noi金牌)是一種神奇的資料結構,也叫非旋treap,它不像treap zig zag搞不清楚(所以叫非旋嘛),也不像splay完全看不懂,而且它能完成treap與splay能完成的所有事,**短,理解也容易。

fhq treap和treap很像,都是給每個節點乙個隨機的權值,使它滿足堆的性質。建議先了解treap(沒必要實現,懂得原理即可)。不過,如果有兩個節點值相同,fhq treap不會用乙個陣列cnt記錄個數,而是直接再開乙個節點。

fhq的基本操作只有兩個:split與merge。

split表示把一棵樹分成兩棵,merge表示把一棵樹合併成一棵。

int l[maxn], r[maxn], sz[maxn], rk[maxn], val[maxn], tot;

int root;

int new( int v )

#define updata(x) sz[x] = sz[l[x]] + sz[r[x]] + 1

沒寫成結構體,沒寫成指標。

\(l[i]\)表示\(i\)的左兒子,\(r[i]\)表示\(i\)的右兒子,\(sz[i]\)表示以\(i\)為根的子樹包含的節點數,\(rk[i]\)表示為了保持平衡隨機賦予的權值,\(val[i]\)表示該節點儲存的值,\(tot\)表示節點數,\(root\)表示當前的根節點。

\(new(v)\)表示新建乙個值為\(v\)的節點(可以看成一棵只有乙個節點平衡樹)

\(updata(x)\)表示更新節點\(x\)的\(sz\)

怎麼分割呢?

常見的分割方法有兩種,一種是按值分,一種是按排名分(實現差不多,這裡只講按值分)。

先來看看定義。

void split( int c, int k, int &x, int &y );
c表示當前要分割的樹的根節點,並且把值\(\le k\)的節點分割出來,構成一棵樹,把\(x\)賦為根節點,其他節點另外構成一棵樹,把\(y\)賦為其根節點。\(x\)、\(y\)用引用(&)更方便處理。

對於當前的樹,如果根節點\(c\)的值\(\le k\),\(c\)的左子樹也全部\(\le k\),所以我們可以把\(x\)賦為\(c\),保留左子樹,將右子樹\(\le k\)的部分分割出來作為\(x\)的右子樹。剩下的部分自然也就是在\(> k\)的部分。\(>k\)的情況同理。具體我們用遞迴實現。

void split( int c, int k, int &x, int &y )//如果當前處理的樹為空,分出的兩個子樹當然也為空,所以直接賦值返回。

if ( val[c] <= k ) x = c, split( r[c], k, r[x], y );//如果根節點值小於等於k,把x賦為c,繼續處理右子樹,並把小於等於k的部分分到x的右子樹,其他分到y

else y = c, split( l[c], k, x, l[y] );

updata(c);//別忘了更新sz

}

上面分割的操作不會改變堆的性質與二叉查詢樹的性質,但是在合併的時候要注意保持堆的性質。

void merge( int &c, int x, int y );
表示把以\(x\)和\(y\)為根節點的樹合併,將\(c\)賦為根節點。

注意:上面分割時x的所有節點的值都小於y的,合併時也要注意x的所有節點小於等於y,否則會出錯

由於\(x\)與\(y\)的權值在兩顆樹中是最大的,所以合併後的樹根節點不是\(x\)就是\(y\)。所以比較\(x\)與\(y\)的權值就可以判斷誰為根節點。

假設以\(x\)為根。因為保證\(x\)的所有節點的值都小於等於\(y\)的,所以\(y\)肯定會合併在\(x\)的右子樹。所以,我們不用動\(x\)的左子樹,合併\(x\)的右子樹與\(y\)作為\(x\)的右子樹。\(y\)為根時同理。這樣,就巧妙完成了同時維護堆的性質與二叉查詢樹的性質。

我們還是用遞迴。

void merge( int &c, int x, int y )

if ( rk[x] >= rk[y] ) c = x, merge( r[x], r[c], y );

else c = y, merge( l[y], x, l[c] );

updata(c);

}

我剛開始也理解不了這兩種操作。主要瓶頸在難以想象。其實可以看做只處理當前的,未處理的留到下一步,反正操作方法都一樣。

剩下的都可以用這兩種操作實現。

直接把它分成\(\le v\)的樹和\(> v\)的樹,將新建的節點與\(\le v\)的樹合併,再與\(>y\)樹合併即可。

//opt 1

void ins( int v )

分成\(\le k\)和\(> k\)兩顆樹,再分成\(、\(=k\)、\(> k\)三棵樹,將\(=k\)左右子樹合併,相當於刪去\(=k\)的乙個節點,然後將三棵樹重新合併即可。

// opt 2

void del( int v )

其實可以用while迴圈,,,但是,,,我,,,懶,,,所,,,以,,,直,,,接,,,,,,,

//opt 3

int getrankbyval( int v )

這真的不能用split和merge偷懶了,,,所以乖乖寫個while吧~

技術含量不高,自行理解。

//opt 4

int getvalbyrank( int rk )

return -1;//題目沒要求。。。只是為了自己查錯

}

分成兩顆樹\(與\(\ge v\),在\(樹中找最大值即可。

//opt 5

int getpre( int v )

與查詢字首同理。

//opt 6

int getnxt( int v )

洛谷p3369 【模板】普通平衡樹

#includeusing namespace std;

#define maxn 100005

int l[maxn], r[maxn], sz[maxn], rk[maxn], val[maxn], tot;

int root;

int new( int v )

#define updata(x) sz[x] = sz[l[x]] + sz[r[x]] + 1

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

if ( val[c] <= k ) x = c, split( r[c], k, r[x], y );

else y = c, split( l[c], k, x, l[y] );

updata(c);

}void merge( int &c, int x, int y )

if ( rk[x] >= rk[y] ) c = x, merge( r[x], r[c], y );

else c = y, merge( l[y], x, l[c] );

updata(c);

}//opt 1

void ins( int v )

// opt 2

void del( int v )

//opt 3

int getrankbyval( int v )

//opt 4

int getvalbyrank( int rk )

return -1;

}//opt 5

int getpre( int v )

//opt 6

int getnxt( int v )

int t;

int main()

} return 0;

}

fhq treap還可以資瓷可持久化~比treap、splay好用多啦

Fhq Treap 學習筆記

fhq treap 是一種平衡樹,又稱非旋 treap,其特點可以從名字裡明顯看出。fhq treap 具有 短 拓展性強的優點,在 oi 中的用途較廣。對於插入 v 的操作,我們把 treap 拆成 leq v 1 和 geq v 兩部分,接下來把 v 和 leq v 1 的部分合併,再把這一部分...

學習筆記 fhq treap

不需要旋轉,只需要 split 和合併 merge 就可以支援 splay 的所有操作。非常好寫,非常好調。並且支援可持久化 雖然我不會 對於每個點需要乙個附加權值,根據這個附加權值維護乙個小根堆,這樣這棵樹平衡與否是由這個附加權值決定的,那麼這個權值該怎麼取呢?隨機!這樣 treap 就大概是平衡...

fhq treap(無旋treap) 學習筆記

首先最好要會寫treap 也先了解一下笛卡爾樹是什麼。fhq treap和treap同樣有乙個隨機分配的rnd值,用於平衡,但fhq treap不需要旋轉操作來維持平衡,因為有兩個神奇的操作merge和split 在兩種操作之前,要明確的一點是fhq treap依靠rnd值來維護平衡,把每個點按照小...