すべてのクエリの対数時間は達成可能だと思います。主なアイデアは、間隔ツリーを使用することです。ツリーの各ノードは、インデックスの間隔に対応します。簡単なバージョンのデータ構造(getとsetはサポートできますが、他の操作はサポートできません)から始めて、次に他の機能もサポートする機能を追加して、主要なアイデアを構築します。
単純なスキーム(getとsetをサポートしますが、追加やスタブはサポートしません)
関数がで一定の場合、つまり場合、区間はフラットであるとし。f [ a 、b ] f (a )= f (a + 1 )= ⋯ = f (b )[a,b]f[a,b]f(a)=f(a+1)=⋯=f(b)
単純なデータ構造は、間隔ツリーになります。つまり、各ノードが(インデックスの)間隔に対応するバイナリツリーがあります。ツリーの各ノードに対応する間隔を格納します。各リーフはフラットな間隔に対応し、リーフを左から右に読み取ると、連続していないフラットな間隔のシーケンスが得られ、その和集合がすべてます。内部ノードの間隔は、2つの子の間隔の和集合になります。また、各リーフノードに、関数の値を区間格納しますV [ 1 、N ] ℓのV (I(v)v[1,n]ℓF I (ℓ )F FV(ℓ)fI(ℓ)このノードに対応します(この間隔はフラットであるため、は間隔で一定であるため、各リーフノードに単一の値を格納するだけです)。ff
同様に、をフラットな間隔に分割し、データ構造がキーがこれらの間隔の左端であるバイナリ検索ツリーであると想像できます。葉には、が一定であるインデックスの範囲での値が含まれます。f f[1,n]ff
標準の方法を使用して、バイナリツリーのバランスが保たれるようにします。つまり、その深さは(はツリー内の現在の葉数をカウントします)。もちろん、なので、深さは常に最大でもです。これは以下で役立ちます。O(lgm)M ≤ N O (LG N )mm≤nO(lgn)
これで、次のようにgetおよびset操作をサポートできます。
私get(i)は簡単です。ツリーを走査して、間隔にが含まれる葉を見つけます。これは基本的に二分探索木をたどるだけです。深度は、実行時間はです。iO (lg n )O(lgn)O(lgn)
set([a,b],y)はトリッキーです。それはこのように動作します:
まず、我々は、葉の間隔見つける含みます。場合、このリーフ間隔をとの2つの間隔(したがって、このリーフノードを内部ノードにして、2を導入します)。a a 0 < a [ a[a0,b0]aa0<a[ a 、b 0 ][a0,a−1][a,b0]
次に、を含むリーフ間隔を見つけます。場合、このリーフ間隔を2つの間隔と分割します(したがって、このリーフノードを内部ノードにして、2つの子を導入します)。b b < b 1 [ a 1[a1,b1]bb<b1[ b + 1 、b 1 ][a1,b][b+1,b1]
この時点で、区間は、ツリー内のノードのサブセットに対応する区間の結合として表現できると主張します。したがって、それらのノードの子孫をすべて削除し(それらをリーフに変換)、それらのノードに格納されている値を設定します。O (lg n )O (lg n )y[a,b]O(lgn)O(lgn)y
最後に、ツリーの形状を変更したので、必要な回転を実行して、ツリーのバランスを再調整します(ツリーのバランスを保つための標準的な手法を使用します)。
この操作にはノードに対するいくつかの単純な操作が含まれるため(およびそのノードのセットは時間で簡単に見つけることができます)、この操作の合計時間はです。O (lg n )O (lg n )O(lgn)O(lgn)O(lgn)
これは、操作ごとに時間でget操作とset操作の両方をサポートできることを示しています。実際、実行時間はと表示できます。ここで、はこれまでに実行された集合演算の数です。O (lg min (n 、s ))sO(lgn)O(lgmin(n,s))s
追加のサポートの追加
上記のデータ構造を変更して、追加操作もサポートできるようにすることができます。特に、リーフに関数の値を格納する代わりに、ノードのセットに格納された数値の合計として表されます。
より正確には、入力での関数の値は、ツリーのルートから区間に含む葉までのパス上のノードに格納された値の合計として回復できます。各ノードに値格納します。場合葉の祖先を表す(葉自体を含む)を、その後の関数の値になります。i i v V (v )v 0、v 1、… 、v k v k I (v k)f(i)iivV(v)v0,v1,…,vkvkI(vk)V(v0)+⋯+V(vk)
上記の手法の変形を使用して、取得および設定操作をサポートするのは簡単です。基本的に、ツリーを下方向にトラバースするとき、値の実行中の合計を追跡するため、トラバースがアクセスする各ノードについて、ルートからへのパス上のノードの値の合計がわかります。それができたら、上記のgetおよびsetの実装を簡単に調整するだけで十分です。xx
そして今、効率的にサポートできます。最初に、間隔を、ツリー内のノードのセットに対応する間隔の和集合として表します(必要に応じて、左端と右端でノードを分割します) )、setオペレーションのステップ1〜3とまったく同じです。次に、これらのノードのそれぞれに格納されている値にを追加するだけです。(その子孫は削除しません。)add([a,b],δ)[a,b]O(lgn)O(lgn)δO(lgn)
これにより、操作ごとに時間でget、set、addをサポートする方法が提供されます。実際、操作ごとの実行時間はここで、はセット操作の数と追加操作の数をカウントします。O(lgn)O(lgmin(n,s))s
スタブ操作のサポート
刺すクエリはサポートするのが最も難しいです。基本的な考え方は、上記のデータ構造を変更して、次の追加の不変条件を保持することです。
(*)各葉の対応する間隔は、最大フラット間隔です。I(ℓ)ℓ
ここで、間隔は、(i)がフラットであり、かつ(ii)を含む間隔がフラットでない場合(つまり、すべての場合)、最大フラット間隔であると言いますが場合、またははフラットではありません)。[ a 、b ] [ a[a,b][a,b]A '、B ' 1 ≤ ' ≤ ≤ B ≤ B ' ≤ N [ '、B '〕= 〔、B[a,b]a′,b′1≤a′≤a≤b≤b′≤n[a′,b′]=[a,b][a′,b′]
これにより、スタブ操作を簡単に実装できます。
- stab(i)は、間隔にが含まれる葉を見つけ、その間隔を返します。i
ただし、不変(*)を維持するために、セットを変更して操作を追加する必要があります。葉を2つに分割するたびに、隣接する葉の間隔のペアに関数同じ値がある場合、不変式に違反する可能性があります。さいわい、各セット/追加操作では、最大4つの新しいリーフ間隔が追加されます。また、新しい間隔ごとに、そのすぐ左と右にあるリーフ間隔を簡単に見つけることができます。したがって、不変条件に違反していたかどうかがわかります。そうであれば、が同じ値を持つ隣接する区間をマージします。さいわい、2つの隣接する間隔をマージしても、カスケードの変更はトリガーされません(したがって、マージによって不変量の違反がさらに発生したかどうかを確認する必要はありません)。全体として、これには調査が含まれますf 12 = O (1 )O (lg n )ff12=O(1)間隔のペアとそれらをマージする可能性があります。最後に、マージによってツリーの形状が変更されるため、これがバランス不変式に違反する場合は、必要なローテーションを実行して、ツリーのバランスを維持します(バイナリツリーのバランスを維持するための標準的な手法に従います)。合計すると、これにより、最大で追加/追加操作が追加されます。O(lgn)
したがって、この最終的なデータ構造は4つの操作すべてをサポートし、各操作の実行時間はです。より正確な見積もりは操作あたりの時間です。ここで、は、セット操作と追加操作の数をカウントします。O (lg min (n 、s ))sO(lgn)O(lgmin(n,s))s
別れの思い
ふew、これはかなり複雑な計画でした。間違えなかったといいのですが。このソリューションに依存する前に、私の仕事を注意深く確認してください。
add
の部分区間の数は線形になります。遅延圧縮された追加の単項「」ノードを持つスプレイツリーについて考えましたか?+ δ