포스트

BOJ 14755 Pseudoknot

sorohue가 PS하는 블로그

BOJ 14755 Pseudoknot

문제 링크입니다.

문제 요약

다음의 표기를 약속합시다.

  • 문자열 $S$에 대해 $rev(S)$는 $S$를 뒤집은 문자열입니다.

주어진 문자열 $S$를, 빈 문자열이 아닌 두 문자열 $u,v$와 비어 있을 수도 있는 두 문자열 $x, y$에 대해 $u,x,rev(v), rev(u),y,v$를 이 순서대로 이어붙인 형태로 표현하려 합니다.

$\min(\vert u \vert, \vert v \vert)$의 최댓값을 구하세요.

풀이

목표로 하는 답의 값 $m$을 정해 놓고 “$u$와 $v$의 길이를 각각 $m$ 이상으로 해가 존재하는가?”를 문제로 두면 이는 결정 문제이므로, 이분 탐색을 통해 원래 얻고자 했던 최적화 문제의 답을 얻을 수 있습니다.

정해진 $m$에 대해 문제를 해결하는 방법을 생각해 봅시다. $rev(v)$와 $rev(u)$가 $S$ 안에서 서로 붙어있어야 함으로부터, 두 문자열의 방향이 서로 반대라고 생각하면 두 문자열 $rev(u)$와 $rev(v)$가 같은 시작점을 갖는다고 생각할 수 있습니다. 이 상황에서 두 문자열의 길이를 각각 $m$ 이상인 선에서 최대한 짧게 만드는 것이 우리의 목적입니다.

$u$와 $v$는 각각 (한쪽 문자열을 반대 방향으로 돌려서 생각하면) 문자열의 접두사로 생각할 수 있습니다. 따라서 KMP를 이용해 각 위치에서 접두사와 겹치는 가장 긴 패턴을 찾고, 이를 $m$ 이상인 선에서 실패 함수를 따라가면서 줄여나가는 방식으로 각 인덱스에 대해 최소의 $u$와 $v$의 길이를 구할 수 있습니다…만. 그냥 실패 함수를 쓰면 최악의 경우에 한 번의 결정 문제를 해결하는 데에 ${\cal O}(N^2)$의 시간 복잡도가 필요합니다.

이를 해소하기 위해 실패 함수의 희소 배열을 구축한다는 발상을 할 수 있습니다. 이를 바탕으로 하면 각 인덱스에서 필요한 길이의 패턴을 찾는 데에 ${\cal O}(\lg N)$의 시간 복잡도가 필요하므로, 하나의 결정 문제를 ${\cal O}(N \lg N)$에 해결할 수 있고, 따라서 최적화 문제를 ${\cal O}(N \lg^2 N)$에 해결할 수 있습니다.

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll, ll>;
string s;
int fa[20][202020], fb[20][202020], ka[202020], kb[202020], n;

bool solve(int len){
	if(!len) return 1;
	for(int i = 1; i < n; i++){
		int A = ka[n-1-i], B = kb[i-1];
		if(A < len || B < len) continue;
		A--; B--;
		for(int bit = 19; bit >= 0; bit--){
			if(fa[bit][A] >= len) A = fa[bit][A]-1;
			if(fb[bit][B] >= len) B = fb[bit][B]-1;
		}
		A++; B++;
		//cout << i << ' ' << A << ' ' << B << '\n';
		if(A+B <= min(i, n-i)) return 1;
	}
	return 0;
}

int main(){
	cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
	cin >> s; n = s.size();
	for(int i = 1, j = 0; i < n; i++){
		while(j > 0 && s[i] != s[j]) j = fa[0][j-1];
		if(s[i] == s[j]) fa[0][i] = ++j;
	}
	for(int i = 1, j = 0; i < n; i++){
		while(j > 0 && s[n-1-i] != s[n-1-j]) j = fb[0][j-1];
		if(s[n-1-i] == s[n-1-j]) fb[0][i] = ++j;
	}
	
	for(int bit = 1; bit < 20; bit++) for(int i = 1; i < n; i++){
		if(fa[bit-1][i] > 0) fa[bit][i] = fa[bit-1][fa[bit-1][i]-1];
		if(fb[bit-1][i] > 0) fb[bit][i] = fb[bit-1][fb[bit-1][i]-1];
	}
	
	for(int i = 0, j = 0; i < n; i++){
		while(j > 0 && s[n-1-i] != s[j]) j = fa[0][j-1];
		if(s[n-1-i] == s[j]) ka[i] = ++j;
	}
	for(int i = 0, j = 0; i < n; i++){
		while(j > 0 && s[i] != s[n-1-j]) j = fb[0][j-1];
		if(s[i] == s[n-1-j]) kb[i] = ++j;
	}
	int l = 0, r = n;
	while(l < r){
		int mid = l+r>>1;
		if(solve(mid)) l = mid+1;
		else r = mid;
	}
	if(l == 1) cout << -1;
	else cout << l-1;
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.