문제 링크

Manacher Algorithm을 반드시 알아야 풀 수 있는 문제다. 모르는 상태에서도 곰곰히 생각해보면 길이가 2500인 문자열을 알고리즘으로 검사해야 하는데 이것은 시간상 불가능하므로 별도의 알고리즘이 필요하다는 사실을 짐작할 수 있다.

발상

Manacher Algorithm을 통해 문자열의 각 인덱스 별로 해당 인덱스를 중심으로 하는 가장 긴 팰린드롬의 길이를 구했다. 모든 위치에서 가장 긴 팰린드롬의 길이를 알 수 있으므로 현재 문자열 에서 팰린드롬 문자열을 뺀 이전 문자열 들을 알아낼 수 있다. 그리고 재귀적으로 다시 문자열 에서 팰린드롬 문자열을 뺀 이전 문자열 들을 알 수 있다. 이런식으로 계속 팰린드롬 문자열을 빼나가다보면 빈 문자열에 도달할 것이다. 빈 문자열은 기저사례가 된다. 빈 문자열은 0개의 팰린드롬 문자열로 이루어져 있으며, 팰린드롬 문자열을 뺄 때 마다 이전 문자열 를 구성하는 팰린드롬 문자열 수 + 1을 하면 의 팰린드롬 분할 수 를 알 수 있다. 그림으로 나타내면 아래와 같다. 위 그림에서 , , 부분 문자열에서 다시 재귀적으로 팰린드롬 문자열을 찾아내는 과정을 반복한다. 그 후 그 중 가장 작은 팰린드롬 문자열 수에 1을 더한 값이 현재 문자열 를 만드는데 필요한 최소 팰린드롬 분할 수다.

점화식

이 그림의 , , 세 부분 문자열의 마지막 인덱스를 계산한 항이다. 팰린드롬 중심 인덱스 에서 현재 인덱스 와의 차이만큼 이전으로 이동한 인덱스 으로 유도된다.

코드

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
int main()
{
	string s, sp = "#"; cin >> s;
	for (int i = 0; i < s.size(); i++) {
        sp.push_back(s[i]); sp.push_back('#');
	}
    int n = sp.size(); vector<int> res(n);
	int p, r = -1;
    // manacher algorithm
    for (int i = 0; i < n; i++) {
        if (i > r) {
            p = r = i;
            while (r < n && r <= 2 * p && sp[r] == sp[2 * p - r]) r++;
            r--;
            res[i] = r - p;
        }
        else {
            int j = 2 * p - i;
            if (res[j] < r - i) {
                res[i] = res[j];
            }
            else if (res[j] > r - i) {
                res[i] = r - i;
            }
            else {
                p = i;
                while (r < n && r <= 2 * p && sp[r] == sp[2 * p - r]) r++;
                r--;
                res[i] = r - p;
            }
        }
    }
    vector<int> dp(n + 1, (int)21e8); // dp[i] = 인덱스 i까지 최소 팰린드롬 분할 수 
    dp[0] = 0; dp[1] = 0; dp[2] = 1; // 첫 문자까지 기저 사례 취급
    for (int i = 3; i <= n; i++) {        
        dp[i] = dp[i - 1] + (sp[i - 1] != '#' ? 1 : 0); // 보강용 문자는 실재하는 문자는 아니므로 끝에 추가돼도 분할 수가 늘어나지는 않는다.
        for (int j = 2; j < i; j++) {
            if (i - j <= res[j - 1]) {
                dp[i] = dp[i] < dp[j - (i - j) - 1] + 1 ? dp[i] : dp[j - (i - j) - 1] + 1;
            }
        }
    }
    cout << dp[n];
	return 0;
}