Official

G - Prefix Concatenation Editorial by mechanicalpenciI


まず、\(2\leq i\leq |S|\) をみたす各 \(i\) について、\(S\)\(i\)文字目から \(|S|\) 文字目までを抜き出した 文字列と \(S\) 自身の最長共通接頭辞の長さ \(f(i)\) を求める事を考えます。これを求めるには Z-Algorithm というものが知られており、\(O(|S|)\) でこれを求めることができます。これは概ね次のようなアルゴリズムです。以下、 \(S\)\(i\) 文字目を \(S[i]\) で表します。

  • \(i=2\)のときは愚直に比較します。すなわち、\(j=1,2,\ldots\) について \(S[j+1]\)\(S[j]\) を比較し、初めて \(S[j+1]\neq S[j]\) となる\(j\)に対して \(f(2)=j-1\)で定めます。

  • \(3\leq i\leq N\) のとき、\(2\leq l\leq i-1\) であって、\(l+f(l)\) が最大となるようなものを取ります。このとき、(\(S\)\(l\) 文字目から \(l+f(l)-1\) 文字目まで ) \(=\) (\(S\)\(1\) 文字目か\(らf(l)\) 文字目まで )であるため、(\(S\)\(i\) 文字目から \(l+f(l)-1\) 文字目まで ) \(=\) (\(S\)\(i-l+1\) 文字目か\(らf(l)\) 文字目まで )が成り立ちます。

  • よって、\(f(i-l+1)<f(l)-(i-l+1)\) であれば\(f(i)=f(i-l+1)\)となります。

  • そうでないとき、\(j=1,2,\ldots\) について\(S[l+f(l)-1+j]\)\(S[l+f(l)-i+j]\) から順に比較していき、初めて \(S[l+f(l)-1+j]\neq S[l+f(l)-i+j]\) となる\(j\)に対して \(f(i)=l+f(l)-i+j-1\)で定めます。

このとき各 \(f(i)\) を求めるのにかかる比較回数は \(\displaystyle\max_{1\leq j\leq i} (f(j)+j)-\displaystyle\max_{1\leq j\leq i-1} (f(j)+j)\) 回程度であることから 全体で \(|S|\) 回程度となる事が分かります。

このアルゴリズムを元に、今回の問題を解くことができます。 \(S\) について 上で述べたような \(f(x)\) は求まっているものとします。

\(g(i)\)\(T\)\(1\) 文字目から \(i\) 文字目までの文字列を \(S\) の接頭辞の連結として表したときの最小回数とします。求めたいものは \(g(|T|)\) です。 ただし、表せない場合は \(g(i)=\infty\) とします。ここで、 \(g(i)\) が(広義)単調増加であることに注意します。 まず、 \(g(0)=0\) とし、その後 \(i=1,2,\ldots, |T|\) に対して次のように更新することで、配るDPのような形で順に \(g(i)\) の値を求めます。

  • \(i=1\) ならば\(j=1,2,\ldots\) に対して \(S[j]\)\(T[j]\) を比較し、 \(T[j_0]\neq S[j_0]\) となる最小の \(j_0\) 未満のすべての \(j\) に対し、\(g(j)=1\)とする。

  • そうでないとき、\(g(i-1)=\infty\) ならば \(g(i-1)\) は二度と更新されることはなく、最終的にも \(g(N)\geq g(i-1)=\infty\) となるため、ここで打ち切り, \(-1\) を出力します。

  • 上のいずれでもない時、ここで行いたいのは、(\(T\)\(i\)文字目から\(j\) 文字目まで)が (\(S\)\(1\) 文字目から\(j-i+1\)文字目まで)と一致しているようなすべての\(i\leq j\leq |T|\) について、\(g(j)=\min(g(j),g(i-1)+1)\) と変更する事です。ここで、\(g(i)\) の単調増加性から、\(i\)以前の時点ですでに設定された値は \(g(i-1)+1\)以下であるため、\(g(j)=\infty\) であるようなものに対して\(g(j)=g(i-1)+1\) と変更するだけでよいという事が言えます。

  • さて、そのために、\(1\leq r\leq |T|\) であって、\(g(r)<\infty\) となる最大の \(r\)を取ります。また、\(1\leq l\leq i-1\) であって、(\(T\)\(l\) 文字目から\(r\) 文字目まで )\(=\)(\(S\)\(1\) 文字目から \(r-l+1\) 文字目まで ) となるようなものが存在するため、そのような \(l\) を取ります。

  • (\(T\)\(i\) 文字目から \(r\) 文字目 ) までは (\(S\)\(i-l+1\)文字目から \(r-l+1\) 文字目 ) と一致しているため、\(f(i-l+1)<r-i+1\) ならば\(j>r\)に対しては\(g(j)=\infty\) のままなので何も行いません。

  • \(f(i-l+1)\geq r-i+1\) のとき、\(j=1,2,\ldots\)について\(T[r+j]\)\(S[r-i+1+j]\) を比較し、\(T[r+j]\neq S[r-i+1+j]\) となる最小の \(j_0\) 未満のすべての \(j\) に対し、\(g(r+j)=g(i-1)+1\)とする。

このようにして最終的に求まった \(g(|T|)\) を出力すれば良いです。こちらの計算量も比較回数は \(g(r)<\infty\) となる \(r\) の最大値が毎回更新されるので高々 \(O(|T|)\) で、前半のものと合わせて、\(O(|S|+|T|)\) で解くことができました。 これは十分高速です。 c++による実装例

#include <bits/stdc++.h>
using namespace std;

int main() {
	string s, t;
	int c[500010], d[500010];
	int l, r;

	cin >> s;
	cin >> t;
	int n = s.size();
	int m = t.size();

	r = 1;
	c[0] = n;
	for (int i = 1; i < n; i++) {
		if (r == n)c[i] = n - i;
		if (i == 1) {
			while (r < n) {
				if (s[r] == s[r - i])r++;
				else break;
			}
			c[i] = r - i;
			l = i;
		}
		else if (c[i - l] < (r - i)) {
			c[i] = c[i - l];
		}
		else {
			r = max(i, r);
			while (r < n) {
				if (s[r] == s[r - i])r++;
				else break;
			}
			c[i] = r - i;
			l = i;
		}
	}

	for (int i = 0; i < m; i++)d[i] = -1;
	r = 0;
	for (int i = 0; i < m; i++) {
		if (r == m)break;
		if (i == 0) {
			while ((r < m) && (r < n)) {
				if (t[r] == s[r]) {
					d[r] = 1;
					r++;
				}
				else break;
			}
			l = 0;
		}
		else if (d[i - 1] == -1)break;
		else if (c[i - l] >= r - i) {
			while ((r < m) && ((r - i) < n)) {
				if (t[r] == s[r - i]) {
					d[r] = d[i - 1] + 1;
					r++;
				}
				else break;
			}
			l = i;
		}
	}
	cout << d[m - 1] << endl;

	return 0;
}

posted:
last update: