2019年6月26日水曜日

ARC 098 E - Range Minimum Queries

ARC 098 E - Range Minimum Queries

数列A1,A2,...A_1, A_2,...を昇順に並べ替えたものをA1A2...A'_1 \le A'_2 \le ...とする。最大値-最小値をすべて試すなら、だいたい次のようになるはずだ。

  1. 数列(Ai)(A_i)の小さいほうからQQ個取り除いて最大値-最小値を求める。
  2. A1A'_1を残したままにしつつ(Ai)(A_i)からできるだけ小さい数を取り除いてゆき、QQ個取り除けたら最大値-最小値を求める。
  3. A1,A2A'_1, A'_2を残したままにしつつ(Ai)(A_i)からできるだけ小さい数を取り除いてゆき、QQ個取り除けたら最大値-最小値を求める。

これは高々O(N){\mathcal O}(N)ステップで済むので、ひとつひとつのステップがO(N){\mathcal O}(N)O(NlogN){\mathcal O}(N \log N)くらいで実行できればよい。

小さい数を取り除いていく具体的な手順を与えるために、上のステップ3について考えてみる。A1=Ap,A2=AqA'_1 = A_p, A'_2 = A_qとする。

A1,A2,...,Ap1  Ap+1,Ap+2,...,Aq1  Aq+1,Aq+2,...,ANA_1, A_2, ..., A_{p-1} \ | \ A_{p+1}, A_{p+2}, ..., A_{q-1} \ | \ A_{q+1}, A_{q+2},..., A_N

ApA_p, AqA_qを残したまま数を取り除いていくというのは、上の数列の縦線をまたがないように連続したKK個を選んで、その最小値を除いていくということにほかならない。なぜならAp,AqA_p, A_qはほかの数より小さいので、縦線をまたぐとAp,AqA_p, A_qが取り除かれてしまうからだ。つまり、(Ai)(A_i)を複数の独立した数列に分割してそれぞれの最小値を見ればよさそうだ。

さて、「複数の独立した数列」をどう管理すればよいかが問題である。上の3つの部分列をA1,A2,A3{\mathcal A_1}, {\mathcal A_2}, {\mathcal A_3}とする。これらの数列から小さい数を除いていくとき、数列の最小値と長さにしか興味がないので、A1,A2,A3{\mathcal A_1}, {\mathcal A_2}, {\mathcal A_3}はそれぞれ(昇順の)優先度付きキューで表すことができる。さらに、A1,A2,A3{\mathcal A_1}, {\mathcal A_2}, {\mathcal A_3}自身も優先度付きキューQ{\mathcal Q}にいれることにし、Q{\mathcal Q}の順序はAA:{\mathcal A} \le {\mathcal A}' : \Leftrightarrow A{\mathcal A}の最小値\leA{\mathcal A}'の最小値 とする。

一般に、ll個の部分列からQQ個の数を取り除く操作を書き下すと、次のようになる。

  • A1,A2,...,Al{\mathcal A_1}, {\mathcal A_2}, ..., {\mathcal A_l}の中で長さがKK以上であるものだけQ{\mathcal Q}に入れて、残りは捨てる。
  • 次のステップを最大QQ回繰り返す。
    1. Q{\mathcal Q}の先頭から数列A{\mathcal A}を取り出す。
    2. A{\mathcal A}の最小値を取り出す。
    3. A{\mathcal A}の長さがまだKK以上ならQ{\mathcal Q}に戻し、KK未満なら捨てる。
  • Q{\mathcal Q}が空になる前にQQ個の数を取り出せたなら、その最大値-最小値を求める。

以上で答えが求まる。全体の計算量はO(N2logN){\mathcal O}(N^2 \log N)


問題を読んだときはNNが小さいので何をやってもできそうな気がしたけど、具体的な手順を与えるのに膨大な時間をかけてしまった。

2019年6月24日月曜日

JAG Summer Camp 2015 Day 2 F - ほぼ周期文字列

JAG Summer Camp 2015 Day 2 F - ほぼ周期文字列

ローリングハッシュを使う。

文字列ssとインデックスx,yx, yが与えられたとき、ssの接尾辞s[x..]s[x..]s[y..]s[y..]のLCP(最長共通接頭辞)の長さをLCP(x,y)\operatorname{LCP}(x, y)とする。ssの区間[l,r)[l, r)が周期ttの文字列であることは、LCP(l,l+t)r(l+t)\operatorname{LCP}(l, l+t) \ge r-(l+t)であることと同値である。まず、これが成り立てばYesを返してよい。

LCP(l,l+t)<r(l+t)\operatorname{LCP}(l, l+t) < r-(l+t)の場合、文字s[l+LCP(l,l+t)]s[l+\operatorname{LCP}(l, l+t)]s[l+t+LCP(l,l+t)]s[l+t+\operatorname{LCP}(l, l+t)]が異なっている。一方の文字をもう一方の文字に変更すれば周期ttの文字列になる可能性がある。

これを確かめるためには、連続部分列s[l..rt]s[l .. r-t]s[l+t..r]s[l+t .. r]について、s[l+LCP(l,l+t)]s[l+\operatorname{LCP}(l, l+t)]s[l+t+LCP(l,l+t)]s[l+t+\operatorname{LCP}(l, l+t)]に変えたもの(あるいはその逆)のハッシュ値が等しいか調べればよい。


ローリングハッシュを初めて使った。例題と聞いて手をつけたのでLCPを使う方針はすぐに浮かんだが、正しい実装に時間がかかった。以下はメモ。

  • ローリングハッシュに偽陽性(異なる文字列に対して同じハッシュ値を返す)はあるが偽陰性(一致する文字列に対して異なるハッシュ値を返す)はないので、2つの文字列を複数modで比較してひとつでもハッシュ値が異なれば異なる文字列である。
  • LCPの場合、偽陽性は真の値より長い区間をLCPと判定するという形で現れるので、複数modによる最小値をLCPの長さとして採用すればよい。

2019年6月23日日曜日

ARC 038 D - 有向グラフと数

ARC 038 D - 有向グラフと数

与えられたゲームと同じルールで、XX点以上なら先手が勝ちXX点未満なら後手が勝つゲームをG(X)G(X)とする。G(X)G(X)の勝敗が判定できれば、二分探索で答えが求まることになり、判定は後退解析で可能である。後退解析についてはdrkenさんの記事がわかりやすかった。

以下、メモ。

  • ゲームG(X)G(X)で勝負がつかない間、先手は常にXX点未満の頂点を踏み、後手は常にXX点以上の頂点を踏むため、偶数回で強制終了するなら後手勝ち、奇数回なら先手勝ちとなる。
  • 後退解析は最初にキューにいれる頂点が重複すると正しく動かないので注意。

2019年6月21日金曜日

TDPC H - ナップザック

TDPC H - ナップザック

0-basedで考える。また、(wi,vi,ci)(w_i, v_i, c_i)があらかじめ色の昇順にソートされているとする。f(x,y,z)f(x, y, z)を、物 0,1,...,x10, 1, ..., x-1からyy色以下、重さzz以下になるように選んで達成できる最大価値とすると、答えはf(N,C,W)f(N, C, W)である。

ffの漸化式を考える。物xxが色の先頭にない場合は普通のナップサック問題と同じように遷移する:

f(0,y,z)=0f(0, y, z) = 0 f(x,y,z)=max{f(x1,y,z)(x1を入れない)f(x1,y,zwx1)+vx1(x1を入れる) f(x, y, z) = \max \begin{cases} f(x-1, y, z) & (\text{物$x-1$を入れない})\\ f(x-1, y, z-w_{x-1})+v_{x-1} & (\text{物$x-1$を入れる}) \\ \end{cases}

xxがその色の先頭にある時はx1x-1から色が変わるため、新しい色を使う場合と使わない場合を比較しなければならない。p(x)p(x)を物xxのひとつ前の色を持つ品物の先頭とすると、次のように書ける:

f(x,y,z)=max{f(x1,y1,z)(x1の色を使うがx1は入れない)f(x1,y1,zwx1)+vx1(x1の色を使い、x1を入れる)f(p(x),y,z)(p(x), ..., x1の色を飛ばす) f(x, y, z) = \max \begin{cases} f(x-1, y-1, z) & (\text{物$x-1$の色を使うが$x-1$は入れない})\\ f(x-1, y-1, z-w_{x-1})+v_{x-1} & (\text{物$x-1$の色を使い、$x-1$を入れる}) \\ f(p(x), y, z) & (\text{物$p(x)$, ..., $x-1$の色を飛ばす}) \end{cases}

ただし、y=0y=0のときはもう新しい色を使えないのでf(x,0,z)=f(p(x),0,z)f(x,0,z) = f(p(x), 0, z)である。計算量はO(NWC){\mathcal O}(NWC)


以前に見たときは何がなんだかわからなかったが、今見たらあっさり解けた。ソートしてナップサックは典型らしい。