2021年2月13日土曜日

CodeChef - Chef and Array

区間の半数を超える同一要素(dominator)があるかどうか判定するクエリ問題。一点更新もある。

方針 1

Editorialの方針。

2つの区間を合併するとき、ある要素が合併後の区間で半数を超えるためには、2つの区間のうちの少なくともいっぽうで半数を超えていなくてはならない、ということを利用できる。

セグメント木の各ノードに、区間内の各要素の出現頻度を保持するマップと(存在すれば)区間のdominatorを保持しておく。質問クエリに対しては、質問の区間がセグ木上のO(logN){O}(\log N)個の区間に分割できるので、O(logN){O}(\log N)個のdominatorの候補を、O(logN){O}(\log N)個のマップに関して調べる。O(NlogN+Qlog2N){O}(N \log N + Q \log^2 N)だが、ハッシュテーブルを使うO(log2N){O}(\log^2 N)なのでかなり重い。

https://www.codechef.com/viewsolution/42694801 1.63 sec.

方針2

n,kn, kを決めておいて、質問クエリの度に区間からnn回無作為に要素を抽出してkk回以上出現した要素についてrange frequency queryを適用してdominatorかどうか調べる。

正答率を評価するには二項分布のCDFを調べればよい。たとえば100回抽出して17回以上出現した要素について調べることにすると、確率1/2で成功する試行を100回繰り返して成功回数が16回以下になる確率が101110^{-11}以下で、10510^5個の質問に対して少なくとも1回間違える確率は10610^{-6}以下と求まる。

https://www.codechef.com/viewsolution/43903194 0.68 sec.

方針 3

rajat1603さんの書き込みより。

区間内でトーナメントのようなことをする。セグメント木の各ノードに(x,残っているxの数)(x, 残っているxの数)を保持することにして、葉(ai,1)(a_i, 1)から

(x1,c1)(x2,c2)={(x1,c1+c2)if x1=x2(x1,c1c2)else if c1c2(x2,c2c1)otherwise (x_1, c_1) \oplus (x_2, c_2) = \begin{cases} (x_1, c_1 + c_2) \qquad \text {if} \ x_1 = x_2 \\ (x_1, c_1 - c_2) \qquad \text {else if}\ c_1 \ge c_2 \\ (x_2, c_2 - c_1) \qquad \text{otherwise} \\ \end{cases}

という演算でマージしていくと(結合法則は成り立たないが)マージの順番や区間の大きさによらず、dominatorは必ず残る。実際に残った要素がその区間でdominatorかどうか判定するためにはrange frequency queryに答えらればよい。O((N+Q)logN){O}((N+Q) \log N)

https://www.codechef.com/viewsolution/42748384 0.22 sec.

方針4

anudeep2011さんの書き込みより。

区間の長さの閾値BBを適当に決めて、長さBB未満の区間は愚直に判定する。

長さBB以上の区間については、その区間のdominatorは与えられた列中にB/2B/2個より多く含まれることを利用できる。具体的には、列中にB/2B/2個より多く存在する数については個別にBIT等を作っておいて、range frequency queryに答えられるようにする。このような数は高々2N/B2N/B種類しかないので、質問に対しては2N/B2N/B個のBITをすべて調べれば答えられることになる。

前処理: O(NlogN(N/B)){O}(N \log N \cdot (N/B)) 質問クエリ(長さBB未満): O(B){O}(B) 質問クエリ(長さBB以上): O((N/B)logN){O}((N/B) \log N) 更新クエリ: O(logN){O}(\log N)

B=NlogNB = \sqrt { N \log N}とするとO((N+Q)NlogN){O}((N+Q) \sqrt {N \log N})