編集2:
これがあなたの質問に対するさらに別の見方です。
間隔をグラフィカルに考えてみましょう。
1 1 1 2 2 2 3
0-2-4--7--0--3---7-0--4--7--0
[-------]
[-----------------]
[---------]
[--------------]
[-----]
下限の昇順で並べ替えると、上記の interval list のようなものが得られます([2;10];[4;24];[7;17];[13;30];[20;27])
。各下限は、新しい間隔の開始を示し、数値の複製のもう 1 つの「レベル」の開始も示します。逆に、上限はそのレベルの終わりを示し、重複レベルを 1 つ減らします。したがって、上記を次のリストに変換できます。
[2;+];[4;+];[7;+][10;-];[13;+];[17;-][20;+];[24;-];[27;-];[30;-]
最初の値は境界のランクを示し、2 番目の値は境界が下限 ( +
) か上限 ( -
) かを示します。n 番目の要素の計算は、リストに従い、下限または上限に遭遇したときに重複レベルを上げ下げし、重複レベルをカウント係数として使用することによって行われます。
リストを再びグラフィカルに考えてみましょうが、ヒストグラムとして:
3333 44444 5555
2222222333333344444555
111111111222222222222444444
1 1 1 2 2 2 3
0-2-4--7--0--3---7-0--4--7--0
上のビューは最初のビューと同じで、すべての間隔が垂直に詰め込まれています。
1
最初のもの、2番目のものなどの要素です2
。実際、ここで重要なのは、各インデックスの高さであり、各インデックスがすべての間隔の和集合で複製される回数に対応します。
3333 55555 7777
2223333445555567777888
112223333445555567777888999
1 1 1 2 2 2 3
0-2-4--7--0--3---7-0--4--7--0
| | | | | | || | |
ヒストグラム ブロックは区間の下限で始まり、上限または下限の 1 単位前で終了することがわかります。そのため、新しい表記法をそれに応じて変更する必要があります。
n間隔を含むリストを使用して、最初のステップとして、リストを上記の表記 ( O(n) ) に変換し、それを昇順 ( O(nlog(n)) )に並べ替えます。数を計算する 2 番目のステップは、 O (nlog(n) )の合計平均時間に対してO(n) にあります。
これは、'+' と '-' の代わりに1
andを使用した、OCaml での簡単な実装です。-1
(* transform the list in the correct notation *)
let rec convert = function
[] -> []
| (l,u)::xs -> (l,1)::(u+1,-1)::convert xs;;
(* the counting function *)
let rec count r f = function
[] -> raise Not_found
| [a,x] -> (match f + x with
0 -> if r = 0 then a else raise Not_found
| _ -> a + (r / f))
| (a,x)::(b,y)::l ->
if a = b
then count r f ((b,x+y)::l)
else
let f = f + x in
if f > 0 then
let range = (b - a) * f in
if range > r
then a + (r / f)
else count (r - range) f ((b,y)::l)
else count r f ((b,y)::l);;
(* the compute function *)
let compute l =
let compare (x,_) (y,_) = compare x y in
let l = List.sort compare (convert l) in
fun m -> count m 0 l;;
注: - 上記の関数は、求められた数が間隔を超えている場合に例外を発生させます。このまれなケースは、以下の他の方法では考慮されていません。- OCaml で使用されるリストの並べ替え関数はマージ 並べ替えであり、これはO(nlog(n))で効果的に実行されます。
編集:
間隔が非常に大きい可能性があることを考えると、最初に示した解決策 (以下を参照) は最適とはほど遠いものです。代わりに、リストを変換することで処理を大幅に高速化できます。重複するものを検索して間隔リストを圧縮し、間隔のプレフィックス、オーバーラップの数倍、間隔のサフィックスを付けることで置き換えます。次に、リストの各要素がカバーするエントリの数を直接計算できます。上記の分割 (プレフィックス、インフィックス、サフィックス) を見ると、処理を行うための最適な構造はバイナリ ツリーであることがわかります。そのツリーのノードには、オプションでプレフィックスとサフィックスを付けることができます。したがって、ノードには以下が含まれている必要があります。
i
ノードの間隔
i
リスト内の繰り返し回数を示す整数、
- 以下のすべての間隔の左部分木
i
- 上記のすべての区間の右部分木
i
この構造が整っていると、ツリーは自動的にソートされます。そのツリーを具現化する ocaml タイプの例を次に示します。
type tree = Empty | Node of int * interval * tree * tree
ここで、変換アルゴリズムはツリーの構築に要約されます。
この関数は、そのコンポーネントからツリーを作成します。
let cons k r lt rt =
the tree made of count k, interval r, left tree lt and right tree rt
この関数は、ツリーに区間を再帰的に挿入します。
let rec insert i it =
let r = root of it
let lt = the left subtree of it
let rt = the right subtree of it
let k = the count of r
let prf, inf, suf = the prefix, infix and suffix of i according to r
return cons (k+1) inf (insert prf lt) (insert suf rt)
ツリーが構築されると、ノードのカウントを使用してツリーの事前順序トラバーサルを実行し、n 番目の要素の計算を高速化します。
以下は私の以前の回答です。
私のソリューションの手順は次のとおりです。
- 各間隔の下限で間隔リストを昇順に並べ替える必要があります
- 間隔を保存するには、両端キュー
dq
(またはある時点で逆になるリスト)が必要です
コードは次のとおりです。
let lower i = lower bound of interval i
let upper i = upper bound of i
let il = sort of interval list
i <- 0
j <- lower (head of il)
loop on il:
i <- i + 1
let h = the head of il
let il = the tail of il
if upper h > j then push h to dq
if lower h > j then
il <- concat dq and il
j <- j + 1
dq <- empty
loop
if i = k then return j
loop
このアルゴリズムは、関連する間隔のみを考慮してi
、和集合内の要素のランクとその要素の値j
の両方をカウントして、単に間隔を反復することによって機能します。目標順位k
に達した場合、値を返却します。
複雑さはおおよそ O(k) + O(sort(l)) です。