geam1113’s diary

主にAtcoderのコンテストの備忘録を書きます。AtCoder緑と水色を行ったり来たりしています。

ABC250E Prefix Equality(ABC250 参加記録)

コンテスト中AC:A〜D
Eはコンテスト後に解説を読み、自分がコンテスト中に考えていた発想で問題なさそうだったので、その方針でなんとかACできました。
E問題について書いたので、記事のタイトルはEをメインにしました。

E - Prefix Equality

自分の実装

こちらの解説と多分同じ考え方だと思います。(多分)

方針

大雑把なイメージです。
(a_1,...,a_i)から得られる集合をS^a_iとします。同様に、(b_1,...,b_j)から得られる集合をS^b_iとします。

S^a_iと等しいS^b_jが存在すると仮定し、その中で最小のjlとします。

j=l,l+1,...と増加させ、b_jを追加していくと、そのうち集合として等しくなくなります(但し、便宜上b_{N+1}Aに含まれない値があるものとします。)。この時のjrとします。

この時、S^a_iに対しては、l\leq j\lt rを満たすS^b_jが全て等しくなります。

よって、各iについて、j区間を前計算しておけば、各クエリにO(1)で答えられます。

前計算の計算量

愚直に各iについて、j区間を求めようとすると、少なくともO(N^2)くらいにはなってしまいます。

しかし、実はi=1,2,..と探索していくと、jの探索範囲も増加するだけになり、結果としてO(N)くらいになります。
具体例と共になぜそうなるか書いていきたいと思います。

前計算の処理

以下の数列を例にします。
A=\{1,2,1,5,2,3\}
B=\{2,1,2,1,3,5,-1\} (-1は番兵)

以下の集合を定義します。

S_A : S^a_iに含まれ、S^b_{j-1}ではまだ見つかっていない値の集合
S_{AB} : S^a_iS^b_{j-1}に共通して存在する値の集合

i=1,2,...,Nについて、Aの先頭i項とBの先頭j項が等しくなるようなj区間を求めます。
初めj=1としておきます。

(1) S_Aa_1=1を追加し、これに対応するj区間を求めます。
b_1 = 2であり、S_Aに一致する値がないので、これに一致するj区間は存在しません。

(2) S_Aa_2を追加します。S_A=\{1,2\}です。
b_1 = 2なので、S_Aから2を削除して、S_{AB}に追加します。b_2=1も同様です。

ここで、S_Aが空になりました。これは、S^a_2 = S^b_2となったことを指します。よって、i=2のときに集合が等しくなる時の下限はj=2です。 下限が見つかったため、上限を調べます。

現在、S_{AB}=\{1,2\}です。
b_3=2,\ b_4=1S_{AB}に含まれるので、S^a_2 = S^b_2  = S^b_3 = S^b_4となります。
次のb_5 = 3S_{AB}に含まれないので、この時点で、S^a_2 \neq S^b_5となります。従って、半開区間[2,5)i=2の時に集合として等しくなるj区間です。

(3) a_3=1S_{AB}に含まれ、更にS_Aが空です。よって、S^a_3 は一個前のS^a_2と等しく、かつ、S^a_2にはj区間が存在していることがわかります。

この場合、S^a_3 = S^a_2であることから、i=3についてのj区間i=2の時と同じになります。

(4) a_4=5S_Aに追加します。この時、新たにj区間を再探索しますが、j=5からの探索として良いです。
なぜなら、b_1,b_2,b_3,b_4a_1,a_2,a_3に含まれるため、S^a_4を集合として等しくするために必要になるからです。また、b_5a_1,a_2,a_3に含まれない最初の値なので、ここから探索を開始する必要があります。

j区間の探索をしますが、b_5=3で、これはS_Aに含まれません。よって、i=4と等しいj区間は存在しません。

(5) a_5=2S_{AB}に存在するので、既にS^a_4,S^b_4の両方に共通して存在しています。そのため、S_Aには追加しません。
j区間の探索をしますが、b_5=3で、これはS_Aに含まれません。よって、i=5と等しいj区間は存在しません。

(6) a_6=3S_Aに追加し、S_A=\{3,5\}となります。

j区間を探索すると、b_5 =3,\ b_6=5と進めると、両方S_Aに含まれるので、S_Aから削除し、S_{AB}に追加します。j=6の時点でS_Aが空になったので、j=6が、S^a_6と等しくなる下限です。
上限ですが、番兵として入れてあるb_7=-1S_{AB}に含まれないので、i=6の時は、[6,7)が該当の区間になります。

以上で、全てのiに対するj区間が求まりました。

例で示したように、jは常に、Ai項目までに含まれない最初の値を指すようにしておきます。
すると、i+1目項までと等しい区間を探す際にjから開始すればよくなります。
なぜなら、S^a_{i+1}と等しい区間を探す際に、S^a_{i+1}S^a_{i}を部分集合として含むため、少なくともS^a_{i}と共通な値のみを含むS^b_{j-1}が必要となるからです。
これで、jは増加させるだけでよいことがわかりました。

提出コード

Zobrist Hash

公式解説にあったので解いてみました。

整数xを乱数に変換します。これをR_xとします。

集合S=\{a_1,...,a_m\}ハッシュ値R_{a_1}\oplus...\oplus R_{a_m}と定義します。
また、数列A,Bi項目までからなる集合から計算されるハッシュ値をそれぞれZ^A_i,\ Z^B_iとします。

するとi番目のクエリは、

  • Z^A_{x_i} =\ Z^B_{y_i}ならyes、そうでなければNo

と判定できます。

Z^A_i,Z^B_iの計算方法については、累積的にXOR和をとっていけばよいのですが、一点注意が必要で、同じ値を2回以上XORを取らないようにします。(偶数回とると、0になるため)

Bonusの問題は、単純に区間のXORをとっても上記の問題を解決できないので、わかりませんでした。

ハッシュの衝突に関してはyoutubeの公式解説では、64bit整数なら\frac{1}{2^{64}}くらいになるそうです。

なお、実装では 、 http://vivi.dyndns.org/tech/cpp/random.html

↑を参考に64bitのメルセンヌ・ツイスタに乱数をseed値として与えて生成しました。

提出コード