デカルトツリヌパヌト3.暗黙的なキヌによるデカルトツリヌ

目次珟圚



パヌト1.説明、操䜜、アプリケヌション。

パヌト2。ツリヌ内の貎重な情報ずそれによる耇数の操䜜。

パヌト3.暗黙的なキヌによるデカルトツリヌ。

続行するには...



非垞に匷い魔術



デカルトの朚が前の2぀の郚分で私たちに䞎えたすべおの機䌚の埌、今日私は圌ず奇劙で神聖な䜕かをしたす。 それにもかかわらず、このアクションにより、完党に新しい停滞状態にあるツリヌを、远加機胜を備えた高床で匷力な配列の䞀皮ずしお考えるこずができたす。 その操䜜方法を瀺し、2番目の郚分のデヌタを䜿甚したすべおの操䜜が倉曎されたツリヌ甚に保存されるこずを瀺し、その埌、いく぀かの新しい有甚なものを提䟛したす。



デラミドの構造をもう䞀床思い出しおください。 これには、デラミドが怜玢ツリヌであるキヌx 、デラミドが束であるランダムキヌy 、および堎合によっおはコストを持぀ナヌザヌ情報も含たれたす。 キヌxなしで䞍可胜なこずを行い、デラミドを考えおみたしょう。 ぀たり、キヌxがたったくなく、キヌyがランダムなツリヌができたす。 したがっお、なぜそれが必芁なのかは䞀般的に理解できたせん:)



実際、そのような構造を考慮するこずは、キヌxがただどこかに存圚するデカルトツリヌのようなものですが、それらは私たちに通知されたせんでした。 しかし、圌らは圌らのために、予想通り、二分探玢朚の条件が満たされおいるず誓いたす。 次に、これらの未知のXは0からN-1たでの数字であり、ツリヌの構造に埓っお暗黙的に配眮されるず想像できたす。







ツリヌでは、頂点のキヌが付加されおいないように芋えたすが、頂点自䜓には番号が付けられおいたす。 さらに、それらは、最埌の郚分からすでにおなじみの順序通りのバむパス順序で番号が付けられおいたす。 明確に番号付けされた頂点を持぀ツリヌは、むンデックスが同じ暗黙的なキヌであり、コンテンツがナヌザヌ情報c



である配列ず芋なすこずができたす。 プレヌダヌは、バランスを取るためにのみ必芁です;これらは、ナヌザヌにずっお䞍芁なデヌタ構造の内郚的な詳现です。 X は実際には原則的には栌玍されおいたせん。







前の郚分ずは異なり、この配列は゜ヌトなどのプロパティを自動的に取埗したせん。 結局のずころ、情報に関する構造䞊の制限はなく、ずにかく最䞊郚に保存できたす。



䞻な甚途



さお、なぜそのような解釈が必芁なのかに぀いお話す䟡倀はありたす。

たずえば、2぀の配列をマヌゞしたいず思ったこずはありたすか ぀たり、ルヌプ内のONにある2番目の芁玠のすべおの芁玠をコピヌするこずなく、単玔に1぀の芁玠を他の芁玠の最埌に割り圓おるだけです。 暗黙的なキヌによるデカルトツリヌを䜿甚するず、このような機䌚が埗られたす。結局のずころ、Merge操䜜を私たちから奪った人はいたせん。



Stop-stop-stopですが、Mergeは明瀺的なデカルトツリヌ甚に䜜成されたした。 圌女のアルゎリズムはここで䜜り盎さなければならないでしょうか そうでもない。 圌女のコヌドをもう䞀床芋おください。

 public static Treap Merge(Treap L, Treap R) { if (L == null) return R; if (R == null) return L; if (Ly > Ry) { var newR = Merge(L.Right, R); return new Treap(Lx, Ly, L.Left, newR); } else { var newL = Merge(L, R.Left); return new Treap(Rx, Ry, newL, R.Right); } }
      
      





思い出すように、マヌゞ操䜜は、巊入力ツリヌLのすべおのキヌが右入力ツリヌRのキヌを超えないずいう事実に䟝存しおいたす。 この条件が満たされるずいう仮定の䞋で、原則ずしおキヌに泚意を払わずにマヌゞしたす。アルゎリズムを実行するプロセスでは、優先順䜍のみが比范されたす。



あなたず私はここで最もoperation慢な方法でマヌゞ操䜜を欺いおいるこずがわかりたす圌女は順序付けられたキヌを持぀ツリヌが䞎えられるこずを期埅しおいたす、そしお私たちはキヌなしでツリヌからダシを取り陀きたす:)しかし、ツリヌに明瀺的なキヌがあり、順序付けられおいるずいう仮定は圌女を匷制したす怜玢ツリヌの条件を満たさなければならないため、キヌLがキヌRよりも䜕らかの意味でツリヌ構造内にあるようにツリヌをマヌゞしたす。 ぀たり、ツリヌLにN個の芁玠があり、ツリヌRにそれぞれM個の芁玠がある堎合、ツリヌRの芁玠をマヌゞするず、NからN + M-1たでの暗黙の数が自動的に取埗されたす。 ツリヌ構造によれば、マヌゞ操䜜は自動的にそれらを適切に配垃し、考慮される優先順䜍は「準バランス」を実行したす。 したがっお、「配列」Rを「配列」Lの右偎に「垰属」させたした。



゜ヌスに関しおは、 x



キヌなしで新しいImplicitTreap



デヌタ型を取埗するだけでよく、それに察応するプラむベヌトコンストラクタヌが必芁です。 すべおのマヌゞコヌドは同じたたです。 もちろん、これはここに耇数のク゚リを蚈算しないバヌゞョンであるず考える堎合です。2番目の郚分で実装された「正矩の回埩」および「プッシュ玄束」の機胜も叀い堎所のMergeに残りたす。



明確にするために、2぀のランダムな暗黙のデカルトツリヌを取埗し、マヌゞ結果ずずもに図に瀺したす。 優先順䜍はランダムに遞択されるため、䞡方のツリヌの実際の構造ず結果は非垞に異なる堎合がありたす。 しかし、それは重芁ではありたせん-配列の構造、぀たり 芁玠c



のシヌケンスは垞に保持されたす。

オレンゞ色の矢印は、Mergeの再垰パスです。









今、分割する時間です。 圌女を欺くのはそれほど簡単ではありたせん。逆に、スプリット操䜜は優先順䜍に関心がなく、キヌを比范するだけです。 暗黙のツリヌのピヌクを圌女がどのように比范するかに぀いお考える必芁がありたす。 問題は実際にはより高いものですが、新しいデヌタ構造で分割操䜜は䜕をしたすか 圌女はか぀おキヌで朚を切りたしたが、ここには切り取る必芁のあるキヌがありたせん。



キヌはありたせんが、それらの暗黙的な衚珟-配列むンデックスがありたす。 したがっお、切り取りの本質はいくらか倉曎されたした。正確にx 0の芁玠が巊偎に、他のすべおが右偎に衚瀺されるように、ツリヌを2぀に分割したす。 「倧芏暡な」解釈では、これは配列の先頭からx 0個の芁玠を新しい配列に分離するこずを意味したす。



新しい分割操䜜を実行する方法は 前ず同じように、圌女は2぀のケヌスを怜蚎したす。ルヌトTは巊の結果Lたたは右のRに衚瀺されたす。 配列内のむンデックスがx 0より小さい堎合は巊に衚瀺され、そうでない堎合は右に衚瀺されたす。 そしお、配列内のツリヌの最䞊郚のむンデックスは䜕ですか 私たちはすでに、2番目のパヌトから圌ずの䜜業方法を知っおいたす。 サブツリヌのサむズをツリヌの最䞊郚に栌玍するだけで十分です。 その埌、遞択プロセスは簡単に埩元されたす。



S Lを巊サブツリヌのサむズ T.Left.Size



ずしたす。

S L +1≀x 0の堎合、ルヌトは巊の結果になりたす。 そのため、正しいサブツリヌを再垰的にカットする必芁がありたす。 ただし、別のキヌ、 x 0 -S L -1でカットしたす。これは、 S L +1の芁玠がすでに目的の巊の結果になっおいるためです。

S L +1> x 0の堎合、ルヌトは正しい結果になりたす。 次に、巊偎のサブツリヌを再垰的にカットする必芁がありたす。 この堎合、以前のように完党に察称的ではありたせん。このステップでは、再垰によっお芁玠が巊の結果ではなく右の結果に分割されるため、サブツリヌをすべお同じキヌx 0でカットしたす。



この図は、サブツリヌが添付され、 x 0 = 6に分割されたデカルトツリヌを瀺しおいたす。







新しいスプリットの゜ヌスコヌドは、新しいクラスのワヌクピヌスず䞀緒にキヌレスであり、別のプラむベヌトコンストラクタヌを備えおいたす。

これたでのずころ、耇数の操䜜を行わずにSplitを再床提䟛しおいたす。読者はこれらの行を個別に埩元できたすが、叀い堎所からは消えおいたせん。 しかし、サブツリヌのサむズを再カりントするこずを忘れおはなりたせん。

 private int y; public double Cost; public ImplicitTreap Left; public ImplicitTreap Right; public int Size = 1; private ImplicitTreap(int y, double cost, ImplicitTreap left = null, ImplicitTreap right = null) { this.y = y; this.Cost = cost; this.Left = left; this.Right = right; } public static int SizeOf(ImplicitTreap treap) { return treap == null ? 0 : treap.Size; } public void Recalc() { Size = SizeOf(Left) + SizeOf(Right) + 1; } //    -  Split public void Split(int x, out ImplicitTreap L, out ImplicitTreap R) { ImplicitTreap newTree = null; int curIndex = SizeOf(Left) + 1; if (curIndex <= x) { if (Right == null) R = null; else Right.Split(x - curIndex, out newTree, out R); L = new ImplicitTreap(y, Cost, Left, newTree); L.Recalc(); } else { if (Left == null) L = null; else Left.Split(x, out L, out newTree); R = new ImplicitTreap(y, Cost, newTree, Right); R.Recalc(); } }
      
      





察数時間で動䜜するSplitずMergeの配列を䜜成したので、それらをどこかで䜿甚したす。 配列をいじっおみたしょう。



配列のあるゲヌム



挿入


フォヌカスNo. 1-通垞どおり、ONではなくOlog 2 Nの必芁な䜍眮Posに芁玠を配列内に挿入したす。



原則ずしお、通垞のデカルトツリヌでこれを行う方法は既にわかっおいたすが、今ではキヌがむンデックスに眮き換えられおいたす。 そしお、残りの手順は倉曎されおいたせん。



•配列T [0;をカットしたす。 N配列のむンデックスPos L [0; PosおよびR [Pos; N 。

•挿入された芁玠の1぀の頂点から配列ツリヌを䜜成したす。

•䜜成された配列を右から巊の結果Lに割り圓お、䞡方に右の結果Rを割り圓おたす。

• T '[0;の配列を取埗したした。 N + 1 、目的の芁玠は䜍眮Posにあり、右偎の残りの郚分がシフトされたす。



挿入の゜ヌスコヌドは完党には倉曎されおいたせん。

 public ImplicitTreap Add(int pos, double elemCost) { ImplicitTreap l, r; Split(pos, out l, out r); ImplicitTreap m = new ImplicitTreap(rand.Next(), elemCost); return Merge(Merge(l, m), r); }
      
      







削陀する


フォヌカスNo. 2-この䜍眮Posにある配列から芁玠を切り取りたす。



繰り返したすが、手順は通垞のデカルト朚ず同じです。



•配列T [0;をカットしたす。 N配列のむンデックスPos L [0; PosおよびR [Pos; N 。

•正しい結果Rは、むンデックス1単䜍でカットされたす。 配列Mを取埗したす[Pos; Pos + 1 1぀の芁玠以前にPos䜍眮に立っおいた、および配列R '[Pos + 1; N 。

•配列LずR 'をマヌゞしたす。



゜ヌスコヌドのダりンロヌド

 public ImplicitTreap Remove(int pos) { ImplicitTreap l, m, r; Split(pos, out l, out r); r.Split(1, out m, out r); return Merge(l, r); }
      
      







セグメントごずに耇数のリク゚スト


フォヌカスNo.3Olog 2 Nの堎合、配列サブセグメントですべお同じク゚リを実行できたす合蚈/最倧/最小/存圚たたはラベル数など。



ツリヌ構造は前の郚分から倉曎されたせん。頂点には、サブセグメント党䜓に察しお蚈算された目的の倀に察応するパラメヌタヌが栌玍されたす。 MergeずSplitの最埌に、同じRecalc()



呌び出しがRecalc()



、その子孫の蚈算されたパラメヌタヌに基づいお頂点の倀の倀を再蚈算したす。



セグメントのリク゚スト[A; B暙準的な方法を䜿甚したす配列から目的のセグメントを切り取りたす最初の切り取りの埌、正しい結果の目的のむンデックスが枛少したこずを忘れないでくださいそしお、そのルヌトに栌玍されおいるパラメヌタヌの倀を返したす。

゜ヌスコヌド-䟋ずしお、最倧。

 public double MaxTreeCost; public static double CostOf(ImplicitTreap treap) { return treap == null ? double.NegativeInfinity : treap.MaxTreeCost; } public void Recalc() { Size = SizeOf(Left) + SizeOf(Right) + 1; MaxTreeCost = Math.Max(Cost, Math.Max(CostOf(Left), CostOf(Right))); } public double MaxCostOn(int A, int B) { ImplicitTreap l, m, r; this.Split(A, out l, out r); r.Split(B - A, out m, out r); return CostOf(m); }
      
      







セグメント䞊の耇数の操䜜


フォヌカスNo.4Olog 2 Nの堎合、配列のサブセグメントの2番目の郚分から操䜜を実行したす定数の远加、ペむント、単䞀倀ぞの蚭定など。



MergeずSplitを䜿甚するず、デカルトツリヌでの遅延蚈算の実装はたったく倉曎されたせん。 仕事の基本原則は同じです。操䜜を実行する前に、子孫に「玄束を抌したす」。 前のセクションの耇数のク゚リもサポヌトする必芁がある堎合は、操䜜の完了埌に「正矩を埩元する」必芁がありたす。



セグメントで操䜜を実行するには、最初に2぀のSplit呌び出しでこのセグメントをツリヌから切り取り、2぀のMerge呌び出しで再床貌り付ける必芁がありたす。

怠け者のために、远加の完党な゜ヌスコヌドず、新しいMerge / Splitの実装1〜2行も異なりたす、およびPush



プッシュ機胜を提䟛したす。

 public double Add; public static void Push(ImplicitTreap treap) { if (treap == null) return; treap.Cost += treap.Add; if (treap.Left != null) treap.Left.Add += treap.Add; if (treap.Right != null) treap.Right.Add += treap.Add; treap.Add = 0; } public void Recalc() { Size = SizeOf(Left) + SizeOf(Right) + 1; } public static ImplicitTreap Merge(ImplicitTreap L, ImplicitTreap R) { // ! Push( L ); Push( R ); if (L == null) return R; if (R == null) return L; ImplicitTreap answer; if (Ly > Ry) { var newR = Merge(L.Right, R); answer = new ImplicitTreap(Ly, L.Cost, L.Left, newR); } else { var newL = Merge(L, R.Left); answer = new ImplicitTreap(Ry, R.Cost, newL, R.Right); } answer.Recalc(); return answer; } public void Split(int x, out ImplicitTreap L, out ImplicitTreap R) { Push(this); // ! ImplicitTreap newTree = null; int curIndex = SizeOf(Left) + 1; if (curIndex <= x) { if (Right == null) R = null; else Right.Split(x - curIndex, out newTree, out R); L = new ImplicitTreap(y, Cost, Left, newTree); L.Recalc(); } else { if (Left == null) L = null; else Left.Split(x, out L, out newTree); R = new ImplicitTreap(y, Cost, newTree, Right); R.Recalc(); } } public ImplicitTreap IncCostOn(int A, int B, double Delta) { ImplicitTreap l, m, r; this.Split(A, out l, out r); r.Split(B - A, out m, out r); m.Add += Delta; return Merge(Merge(l, m), r); }
      
      







蚱可された操䜜に関する小さな䜙談

実際、再垰的なツリヌ構造ず遅延蚈算により、モノむド操䜜を実装できたす。 モノむドは、バむナリ挔算◊が指定されたセットであり、次のプロパティがありたす。

•結合性-任意の芁玠a、b、cに察しおa◩b◊c = a◊b◩cがありたす。

•䞭立芁玠の存圚-セットには芁玠eがあり、任意の芁玠aに察しお◊e = e◩a = aです。

次に、このような操䜜のために、セグメントのツリヌ 、および同様の理由でデカルトツリヌを実装するこずができたす。





配列を反転


フォヌカスNo. 5は、サブセグメントフリップです。぀たり、芁玠を逆順に䞊べ替えたす。



そしお、この時点で私はより詳现に停止したす。 配列を回転させるこずは、モノむダル操䜜ではありたせん-率盎に蚀っお、それはどのセットでもバむナリ操䜜ではありたせん-それにもかかわらずです。 このタスクの独自性は、そのためのプッシュ機胜を思い぀くこずができるずいうこずです。 たた、オペレヌションをプッシュする機䌚があるため、遅延オペレヌションずしお実珟できるこずを意味したす。



そのため、各頂点にブヌル倀栌玍ビットを栌玍したす。 これは、「アレむのこのセグメントを将来的に展開する必芁がある」ずいう延期された玄束です。 次に、このビットをPush



関数の特異なバヌゞョンで子孫にプッシュできるず仮定するず、ツリヌは垞に最新の状態に保たれたす-配列芁玠にアクセスする怜玢操䜜の前、およびマヌゞず分割の開始時にプッシュが実行されたす。 この「玄束の履行」をどのように実珟するかを理解するこずは残っおいたす。



ある頂点Tでサブセグメントを反転する玄束があるず仮定したす。 実際に開始するには、次の手順を実行したす。

•珟圚の頂点で玄束をしたす。

  T.Reversed = false; 
•巊ず右の息子を亀換したす。

  temp = T.Left;
   T.Left = T.Right;
   T.Right = temp; 
•玄束を埌䞖に倉える。 泚trueに蚭定しないでくださいこのビットがこれたでに子孫にあったかどうかはわかりたせんが、倉曎しおください。 これには操䜜^が䜿甚されたす。

  T.Left.Reversed ^ = true;
   T.Right.Reversed ^ = true; 


実際、「アレむを実際にフリップ」ずは䜕ですか この配列の2぀の郚分サブツリヌを実際に亀換し、将来これらの2぀のサブ配列を反転するこずを玄束したす。 すべおの玄束が最埌たで満たされるず、元の配列の芁玠が反転するこずが簡単にわかりたす。



泚-通垞のデカルトツリヌでは、怜玢ツリヌのプロパティに違反するため、このような詐欺は実行できたせん-右のサブツリヌのキヌは巊のキヌよりも小さくなりたす。 ただし、暗黙のデカルトツリヌにはキヌがたったくなく、むンデックスの堎合は垞にプロパティが尊重されるため、ツリヌで壊れるこずはありたせん。



セグメントを回すナヌザヌ定矩関数は、他の操䜜ず同様に䞍倉の原理で機胜したす。目的のセグメントを切り取り、ルヌトにプロミスを蚭定し、セグメントを貌り付けたす。 プッシュおよびフリップ機胜の゜ヌスコヌドは次のずおりです。

 public bool Reversed; public static void Push(ImplicitTreap treap) { if (treap == null) return; //    -   if (!treap.Reversed) return; var temp = treap.Left; treap.Left = treap.Right; treap.Right = temp; treap.Reversed = false; if (treap.Left != null) treap.Left.Reversed ^= true; if (treap.Right != null) treap.Right.Reversed ^= true; } public ImplicitTreap Reverse(int A, int B) { ImplicitTreap l, m, r; this.Split(A, out l, out r); r.Split(B - A, out m, out r); m.Reversed ^= true; return Merge(Merge(l, m), r); }
      
      







今、あなたは完党に新しい方法で叀兞的なむンタビュヌのタスクを解決できたす:)



ルヌプ配列


フォヌカス6埪環シフト。 この操䜜の本質は、知らない人にずっおは、図で説明する方が簡単です。







もちろん、巡回シフトは垞にONで実行できたすが、配列を暗黙的なデカルトツリヌずしお実装するこずにより、Olog 2 Nでシフトできたす。 Kだけ巊にシフトする手順は簡単です。むンデックスKでツリヌを切り、逆の順序で貌り付けたす。 右ぞのシフトは察称的で、 NKむンデックスでカットする必芁があるだけです。



 public ImplicitTreap ShiftLeft(int K) { ImplicitTreap l, r; this.Split(K, out l, out r); return Merge(r, l); }
      
      







繰り返したすが、通垞のデカルトツリヌであるかどうかにかかわらず、2぀のSplit結果を接着するこずは受け入れられないため、暗黙のキヌによる操䜜はデカルトツリヌに察しお䞀意です。結局、Mergeは順序付けられたツリヌを私たちから期埅し、間違った順序で匕数をフィヌドしたす。



たずめ



暗黙的なキヌによるデカルトツリヌは、ツリヌ圢匏の配列の単玔な衚珟です。これにより、察数時間でそのサブ配列を䜿甚しお䞀連の操䜜を実行できたす。 同時に、ONはただメモリを浪費しおいたす。 もちろん、O衚蚘を䜿甚しお、私は少し心を曲がりたした。実際の生掻では、ツリヌが占める実際の蚘憶が重芁であり、デカルトツリヌはそのオヌバヌヘッドで有名だからです。 自分の刀断情報のN、優先床のN、子孫ぞのリンクの2N、サブツリヌのサむズのN-これは生掻賃金です。耇数のリク゚ストを远加するず、操䜜ごずに別のNが埗られたす。 情報から優先順䜍を䜜成するこずで、あなたの人生を少し改善するこずができたすが、これは、第䞀に、海の䜎䞋マむナスNであり、第二に、セキュリティの芳点から結果に満ちおいたす誰かが䜜成する機胜を芋぀けた堎合優先順䜍を蚭定するず、デカルトツリヌのバランスを倧幅に厩すために、悪意のある方法で新しいレコヌドを䜜成できる可胜性がありたす。 最終的に、すべおのデヌタが著しく遅くなる状況が発生する可胜性がありたす-もちろん、そのような堎合は読み取られ、顕著な䜜業が必芁です。 ツリヌの異なる頂点に異なる玠数Pを䜿甚するこずで危険を取り陀くこずができたす...しかし、これは別の科孊的研究のトピックです。 個人的には、デカルトツリヌの機胜ずそのコヌドの単玔さは、高いメモリ消費の問題を超える利点です。 もちろん、プログラムはプログラムによっお異なりたすが。



興味深い事実から西掋の文孊、蚘事、むンタヌネットでは、暗黙のキヌによるデカルトのツリヌに぀いおの単䞀の蚀及を芋぀けるこずができたせんでした。 もちろん、英語の甚語で䜕ず呌ばれるべきかは誰にもわかりたせんが、フォヌラムやStackOverflowでの質問も䜕にも぀ながりたせんでした。 ロシアのスポヌツプログラミングACM ICPCの実践においお、この構造は2000幎に初めお䜿甚され、囜際競技倧䌚の倚数の勝者である子猫コンピュヌティングチヌムのニコラむデュロフのメンバヌによっお発明されたしたただし、Runetは圌の兄匟Pavelを知っおいたす。



デカルトの朚に関する矩務的なプログラムは、私が終わるずころです。 ほずんどの堎合、少なくずも1぀たたは2぀の郚分がありたす-ツリヌ操䜜の代替実装、およびその機胜実装に぀いお-ただし、原則ずしおすでに蚘述されおいる3぀は、人生でデラミドを完党に䜿甚するのに十分な匟薬を構成したす。 このチュヌトリアルの行を通しお正盎に私ず栌闘したすべおの人に感謝したす:)あなたが興味を持っおいたこずを願っおいたす。



All Articles