URL:https://www.luogu.org/problem/P3369
質問の意味:
各$ O(LOGN)(1 \当量のn \の当量の1E6)$、以下の機能のデータ構造の調製:
シーケンス内の番号を削除し、2;第三に、大型の$ K $を見つける;第四に、呼掛け$ $ Kの多数;、シーケンス番号に挿入されるVを、X $ $は、前駆体、6を見つけます$のx $の後継者を見つけます。
ソリューション:
明らかに、二分探索木は、このタスクを達成することができますが、最悪の場合には、バイナリツリーがチェーンに退化だろう、それはタイムアウトしますので、我々は書くことがより簡単でバランスの取れたバイナリツリーを、必要とする、速いスピードでtreapです。treap非スピンに分け、treap treapスピン、前者は高速ですが、遅い持続性、が、永続的なサポートをサポートしていません。
まず、スピンtreapがあります
Treap右ノードはバランスをとるために、バイナリの性質の値を維持し、スタックを維持するために、追加のプロパティの数でバランスが取れており、この番号は、各ノードに対応する乱数であることが必要です。回転乱数に回転するときに示すように、左右の手に分けます:
(参考ブログ:https://blog.csdn.net/K346K346/article/details/50808879)
左と右の手
明らかに右のサブツリー、右利きの子供がルートになって、元の樹木の根の左、左サブツリー、Lの共感の元のルートへの元の左のサブツリーのルートです。バイナリツリーを回転させた後、変更の性質を証明することは容易です。
挿入されたとき、第一挿入ポイントを見つけ、再び回転させました。
より複雑な削除は、削除、検索し、削除後のサブツリーのルートとしてサブツリーを観察し、ランダムな値を選択する点を削除し、ノードの回転により葉を除去するための移動後削除します。
コードを見て、理解しやすい照会操作。
ACコード:
#include <ビット/ STDC ++ H> 名前空間STDを使用して、 const int型MAXN = 100005; const int型INF = 0x3f3f3f3f。 構造体Treap { 構造体ノード { int型のVal、RND、LC、RC、サイズ、NUM。 }。 int型CNT = 0; ノードTR [MAXN]。 ボイドのinit() { CNT = 0。 } int型_rand() { 静的INTシード= 12345。 シード=(INT)シードを返す* 482711LL%2147483647。 } ボイド押し上げ(INT P) { TR [P] .size = TR [TR [P]を得た。LC] .size + T R [TR [P] .RC] .size + TR [P] .num。 } ボイド右(INT&K) { INT TMP = T R [k]を得た。LC。 T R [k]を得た。LC = TR [TMP] .RC。 TR [TMP] .RC = K。 TR [TMP] .size = TR [K] .size。 突き上げ(K)。 K = TMP。 } 左空隙(INT&K) { int型TMP = TR [K] .RC。 TR [K] .RC = TR [TMP]を得た。LC。 TR [TMP]を得た。LC = K。 TR [TMP] .size = TR [K] .size。 突き上げ(K)。 K = TMP。 } ボイドインサート(INT&P、INT X) { IF(P == 0) { P = ++ CNT。 TR [P] .val = xと; TR [P] .num = TR [P] .size = 1。 返します。 TR [P]を得た。LC = TR [P] .RC = 0。 TR [P] .rnd = _rand()。 } ++ TR [P] .size。 もし(x == TR [P] .val) ++ TR [P] .num。 他の場合(X <TR [P] .val) { インサート(TR [P]を得た。LC、X)。 IF(TR [TR [P]を得た。LC] .rnd <TR [P] .rnd) 右(P)。 } そうであれば(X> TR [P] .val) { インサート(TR [P] .RC、X)。 (TR [TR [P] .RC] .rnd <TR [P] .rnd)であれば 、左(P)。 } } ボイドデル(INT&P、INT X) { IF(P == 0) のリターン; IF(TR [P] .val == X) { 場合(TR [P] .num> 1) --tr [P] .num、 - TR [P] .size。 他 { (TR [P]を得た。LC == 0 || TR [P] .RC == 0)であれば 、P = T R [P]を得た。LC + T R [P] .RC。 そうであれば(TR [TR [P]を得た。LC] .rnd <TR [TR [P] .RC] .rnd) 右(P)、デル(P、x)は、 (TR [TR [P]を得た。LC] .rnd> TR [TR [P] .RC] .rnd)もしそうでなければ 、左(P)、デル(P、x)は、 } } そうでなければ(TR [P] .val <x)の場合 --tr [P] .size、デル(TR [P] .RC、X)。 他 --tr [P] .size、デル(TR [P]を得た。LC、X)。 } INT queryrnk(INT&P、INT X) { (p == 0)場合 0を返します。 そうであれば(TR [P] .val == X) TR [TR [P]を得た。LC] .size + 1を返します。 そうであれば(TR [P] .val <x)を 返すTR [TR [P]を得た。LC] .size + T R [P] .num + queryrnk(TR [P] .RC、X)。 他 戻りqueryrnk(TR [P]を得た。LC、X)。 } INT querynum(INT&P、INT RNK) { IF(P == 0) 戻り0; IF(TR [TR [P]を得た。LC] .size> = RNK) 戻りquerynum(TR [P]を得た。LC、RNK)。 rnk- = TR [TR [P]を得た。LC] .size。 IF(RNK <= TR [P] .num) 戻りTR [P] .val。 rnk- = TR [P] .num。 querynum(TR [P] .RC、RNK)を返します。 } int型queryfront(INT&P、INT X) { もし(P == 0) 戻り-INF。 IF(TR [P] .val <x)を リターンMAX(TR [P] .val、queryfront(TR [P] .RC、X)); そうであれば(TR [P] .val> = X) リターンqueryfront(TR [P]を得た。LC、X)。 } INT queryback(INT&P、INT X) { IF(P == 0) 戻りINF。 IF(TR [P] .val> X) リターン分(TR [P] .val、queryback(TR [P]を得た。LC、X)); そうであれば(TR [P] .val <= x)の 戻りqueryback(TR [P] .RC、X)。 } }。 int型のPOS。 TreapのTR; メインINT() { int型のn; scanf関数( "%のD"、&N); int型M、K。 tr.init(); (; iがN <++ iが0 = INT)のために { scanf関数( "%dの%のD"、&M、およびK); IF(M == 1) tr.insert(POS、K)。 他(M == 2)もし tr.del(POS、K)。 他(M == 3)場合 のprintf( "%d個の\ n"、tr.queryrnk(POS、K))。 他(M == 4)場合 のprintf( "%d個の\ n"、tr.querynum(POS、K))。 他(M == 5)場合 のprintf( "%d個の\ n"、tr.queryfront(POS、K))。 他(M == 6)場合 のprintf( "%d個の\ n"、tr.queryback(POS、K))。 } 0を返します。 }