O(lg N)で範囲の更新とクエリを実行できる2つの一般的なデータ構造に関するチュートリアルをいくつか読みました: セグメントツリー と バイナリインデックスツリー (BIT/Fenwick Tree )。
私が見つけた例のほとんどは、「範囲内の整数の合計」、「範囲内のXOR整数」などのような結合法則と可換演算に関するものです。
これらの2つのデータ構造(または他のデータ構造/アルゴリズムを提案してください)がO(lg N)で以下のクエリを実行できるかどうか疑問に思いますか? (いいえの場合、O(sqrt N)はどうですか)
整数Aの配列が与えられた場合、範囲[l、r]内の個別の整数の数をクエリします。
PS:使用可能な整数の数が〜10 ^ 5であると仮定すると、used[color] = true
またはビットマスクはできません
例:A = [1,2,3,2,4,3,1]、query([2,5])= 3、ここで範囲インデックスは0ベースです。
はい、オンラインで質問に答える必要がある場合でも、これはO(log n)で行うことができます。ただし、これにはかなり複雑な手法が必要です。
まず、次の問題を解決しましょう。配列が与えられた場合、「インデックス[l、r]内にいくつの数<= xがあるか」という形式のクエリに答えます。これは、マージソートツリーと呼ばれることもあるセグメントツリーのような構造で行われます。これは基本的に、各ノードがソートされたサブアレイを格納するセグメントツリーです。この構造にはO(n log n)メモリが必要です(log n層があり、それぞれにn個の数値を格納する必要があるため)。 O(n log n)にも組み込まれています。ボトムアップで、内側の頂点ごとに、その子のソートされたリストをマージします。
これが例です。 1 5 2 6 8 4 7 1
を元の配列と言います。
|1 1 2 4 5 6 7 8|
|1 2 5 6|1 4 7 8|
|1 5|2 6|4 8|1 7|
|1|5|2|6|8|4|7|1|
これで、O(log ^ 2 n時間)でこれらのクエリに答えることができます。セグメントツリーに対して定期的なクエリを実行し(O(log n)ノードをトラバース)、バイナリ検索を実行して、<= xがいくつあるかを確認します。そのノードで(ここから追加のO(log n))。
これは、 Fractional Cascading 手法を使用して、O(log n)まで高速化できます。これにより、基本的に、各ノードではなくルートでのみバイナリ検索を実行できます。ただし、投稿で説明するほど複雑です。
ここで、元の問題に戻ります。配列a_1、...、a_nがあると仮定します。別の配列b_1、...、b_nを作成します。ここで、b_i =配列内のa_iの次の出現のインデックス、または最後の出現の場合は∞です。
例(1-インデックス付き):
a = 1 3 1 2 2 1 4 1
b = 3 ∞ 6 5 ∞ 8 ∞ ∞
それでは、[l、r]の数を数えましょう。一意の番号ごとに、セグメント内で最後に出現した番号をカウントします。 b_iの概念を使用すると、b_i > r
の場合にのみ、番号の出現が最後であることがわかります。したがって、問題は「セグメント[l、r]にいくつの数> rがあるか」に要約されます。これは、上記で説明したものに簡単に削減されます。
それが役に立てば幸い。
与えられた問題は、平方根分解アルゴリズムとも呼ばれるMoの(オフライン)アルゴリズムを使用して解決することもできます。
全体的な時間計算量はO(N * SQRT(N))です。
詳細な説明については mos-algorithm を参照してください。複雑さの分析と、このアプローチで解決できるSPOJの問題もあります。
オフラインでクエリに回答する場合は、従来のセグメントツリー/ BITが引き続き役立ちます。
左から右への入力配列の各値について:
現在の要素の場合、以前に見たことがあれば、1インチずつデクリメントします。
前の位置のセグメントツリー。
範囲[l、r == i]の合計をクエリすることにより、現在のインデックスiで終わるクエリに応答します。
要するに、右向きのインデックス、個々の要素の最新の出現をマークし、前の出現を0に戻すという考え方です。範囲の合計は、一意の要素の数を示します。
全体的な時間計算量もnLognになります。
この問題を解決するためのよく知られたオフラインメソッドがあります。 nサイズの配列とqクエリがあり、各クエリで、その範囲内の個別の数の数を知る必要がある場合は、O(n log n + q log n)の時間計算量でこのすべてを解決できます。これは、O(log n)時間ですべてのクエリを解決するのと似ています。
RSQ(範囲合計クエリ)手法を使用して問題を解決しましょう。 RSQ手法の場合、セグメントツリーまたはBITを使用できます。セグメントツリー手法について説明しましょう。
この問題を解決するには、オフライン手法とセグメントツリーが必要です。さて、オフラインテクニックとは何ですか?オフラインテクニックはオフラインで何かをしている。オフライン手法の問題解決の例では、最初にすべてのクエリを入力し、次にそれらを並べ替えることで、正しく簡単に回答し、最後に指定された入力順序で回答を出力できます。
ソリューションのアイデア:
まず、テストケースの入力を取得し、指定されたn個の数値を配列に格納します。配列名をarray []とし、入力qクエリを取得して、ベクトルvに格納します。vのすべての要素は、3つのフィールドl、r、idxを保持します。ここで、lはクエリの開始点、rはクエリの終了点、idxはクエリの数です。このようなものはn番目のクエリです。次に、クエリのエンドポイントに基づいてベクトルvを並べ替えます。少なくとも10 ^ 5要素の情報を格納できるセグメントツリーがあるとします。また、last [100005]というエリアもあります。数値の最後の位置をarray []に格納します。
最初は、ツリーのすべての要素がゼロで、最後の要素はすべて-1です。次に、array []でループを実行します。ループ内で、array []のすべてのインデックスについてこのことを確認する必要があります。
last [array [i]]は-1かどうか? -1の場合は、last [array [i]] = iと記述し、update()関数を呼び出します。この関数は、セグメントツリーのlast [array [i]]番目の位置に+1を追加します。 last [array [i]]が-1でない場合は、セグメントツリーのupdate()関数を呼び出します。これにより、セグメントツリーのlast [array [i]]番目の位置に1が減算されるか、-1が加算されます。次に、現在の位置を将来の最後の位置として保存する必要があります。そのため、last [array [i]] = iを記述し、update()関数を呼び出す必要があります。この関数は、セグメントツリーのlast [array [i]]番目の位置に+1を追加します。
次に、クエリが現在のインデックスで終了したかどうかを確認する必要があります。つまり、if(v [current] .r == i)です。これがtrueの場合、セグメントツリーのquery()関数を呼び出します。この関数は、v [current] .lからv [current] .rの範囲を返し、合計して、結果をv [current] .idx ^ thインデックスに格納します。 answer []配列。また、currentの値を1ずつインクリメントする必要があります。6。次に、指定された入力順序で最終的な回答を含むanswer []配列を出力します。
アルゴリズムの複雑さはO(n log n)です。