Codeforces 90D (119D) String Transformation

問題

長さnの文字列sと整数i, jに対して、次の関数を定義する。
f(s, i, j) = s[i+1...j-1] + r(s[j...n]) + r(s[0...i])


ただし、+は文字列の結合を、r(s)は文字列の反転を表すものとする。
二つの文字列a, bが与えられる。
f(a, i, j) = bを満たすi, jのうち、iを最大にし、同じiに対してはjを最小にするi, jを求めよ。
存在しない場合は-1 -1を出力せよ。

制約条件

文字列の長さ≦10^6

方針

二つの文字列の長さは同じとしてよい。


iは全通りループをまわす。
a[i+1...n-1]と、b[0...n-i-1]の部分を一致させるようなjを、
短い時間で見つけられればよい。


これは実はO(1)でできて、
r(a)$bという文字列のKMPのfailure linkをp[i]とする。
このとき、j = n - p[2 * n - i - 1]である。


これはjの候補のうち、最も小さいものであるが、
ここでa[i+1...j-1]とb[0...j-i-2]が一致しなかった場合、
これ以上大きいjでも絶対一致しないので、この候補だけvalidであるか調べればよい。


このjがvalidであるかは、
b$aという文字列のz配列を見ればよくて、
(z[i]は、s.substr(i)とsが何文字一致するかを表す配列で、O(n)で前計算できる)
z[n+2+i]が、j-i-1以上になっていればよい。

ソースコード

int *buildZ(const string &s){
	int n = s.size();
	int *z = new int[n];
	int l = 0, r = 0;
	
	memset(z, 0, sizeof(int)*n);
	for(int i = 1; i < n; i++){
		if(i <= r) z[i] = min(z[i - l], r - i + 1);
		while(i + z[i] < n && s[z[i]] == s[i + z[i]]) z[i]++;
		if(i + z[i] - 1 > r){
			l = i;
			r = i + z[i] - 1;
		}
	}
	return z;
}
int* buildFail(const string &s){
	int n = s.size();
	int *p = new int[n];
	p[0] = 0;
	for (int i = 1; i < n; ++i){
		p[i] = p[i - 1];
		while (p[i] > 0 && s[p[i]] != s[i]) p[i] = p[p[i] - 1];
		if (s[p[i]] == s[i]) p[i]++;
	}
	return p;
}

string r(string s){
	reverse(all(s));
	return s;
}

int main(){
	string a, b;
	getline(cin, a);
	getline(cin, b);
	
	if(a.size() != b.size()){
		cout << "-1 -1" << endl;
		return 0;
	}
	
	int n = a.size();
	string s = r(a) + '\0' + b;
	string t = b + '\0' + a;

	int *fail = buildFail(s);
	int *z = buildZ(t);
	
	int ai = -1, aj = -1;
	rep(i, n - 1){
		if(a[i] != b[n - i - 1]) break;
		int len = fail[2 * n - i - 1];
		if(z[n + i + 2] >= n - i - len - 1){
			ai = i;
			aj = n - len;
		}
	}
	cout << ai << " " << aj << endl;
	
	return 0;
}