Senの競技プログラミング備忘録

こけた問題を自分用の解説で載せる。けんちょんさんのブログを目指したい。質的にも量的にも。こけた問題だけに限定するけど

パナソニックプログラミングコンテスト2020 - E - Three Substrings

atcoder.jp

概要

ある文字列sが存在し、次の操作を考える。

  • sの空でない連続部分文字列を1つ選び、これをpとする。pのそれぞれの番目を'?'に置き換えるorそのままにする。

この操作で得られた3つの文字列、a, b, cが存在するとき、元のsの最短の長さを答えよ。

制約: 1 <= |a|, |b|, |c| <= 2000

ACするまで

1. 文字列は3つしかないので、3!=6通りの順番全部試して、その順に挿入していけばよくね?
2. 文字列に完全に含められるのならば、完全に含めて、そうでなければできるだけ長さが短くなるように合体させる。
3. 実装していて、時間が足りねーとなって終了


4. 直感的に、次のような greedy が思いつきやすいかもしれません。(中略)実はこれには反例があります。(by 公式editorial)
自分の解法は嘘解法でしたぁ!
5. そうでなければできるだけ長さが短くなるように合体させる。->これは最善とは限らないのです。
6. 貪欲に飛びつく前に考えるべきは、愚直で間に合うか否か?今回は愚直でやれば(工夫をすれば)間に合う!
7. りんごさんの大変洗練されてる実装を参考にしながらAC。

考察

競プロの基本は全探索!(素振り)

基礎体力がないと今回のような問題の全探索は見抜けない。

ひとまず、1つの文字列(ここではa)を固定して、ほかの2つはそいつからどれぐらいズレてるか?を全探索するとどうなるかを考える。それぞれの長さがたかだか2000なので、-4000から4000までありうる。(ここで、連続してない場合などについては考えなくていい。なぜならば、連続してない部分を削ったものもこの全探索の中に含まれていて、正しい答えを出すため)

これでは2乗の計算量になる。

あとやるべきことは、その状態で条件を満たすのか?を判定することである。
愚直にやると、判定にも線形時間がかかるので、結局3条になり険しい。

しかし、判定が依存してるのは、aとの相対位置である。つまり、相対位置が同じのものはわざわざ何度も計算する必要がない。

また、3つしか文字列がないので、一致するかは、
aとbはどうか?bとcはどうか?cとaはどうか?
の3つを調べればよい。

というわけで、2乗の計算量で実装できる。

実装上のテクニックとしては、aとの相対位置ごとに一致を探すのもいいが、実装は大変である。
重複してない部分はノーカン、毎回1文字ずつ照合するものをずらす(それはそう)という2つの性質から、

for (int i = 0; i < A; i++)
		for (int j = 0; j < B; j++)
			if (!match(a[i], b[j]))ab[50000 + i - j] = true;

このように書くと簡潔にすべてのオーバーラップできる。実装上のテクニックとして覚えたほうがいい。

ACソースコード
Submission #10898306 - Panasonic Programming Contest 2020

一言

久々の通常記事だなぁ(サボリ)