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值來維護平衡,把每個點按照小...