ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • BOJ 1509: 팰린드롬 분할
    공부(Archive)/BOJ 2020. 5. 6. 20:54

     

     

    https://www.acmicpc.net/problem/1509

     

    1509번: 팰린드롬 분할

    세준이는 어떤 문자열을 팰린드롬으로 분할하려고 한다. 예를 들어, ABACABA를 팰린드롬으로 분할하면, {A, B, A, C, A, B, A}, {A, BACAB, A}, {ABA, C, ABA}, {ABACABA}등이 있다. 분할의 개수의 최솟값을 출력하는 프로그램을 작성하시오.

    www.acmicpc.net

    문제를 간단히 요약하자면 주어진 문자열을 부분 문자열들로 나누는데 모든 부분 문자열이 팰린드롬이어야 한다. 이때 부분 문자열의 최소 개수를 구하는 문제이다.

     

    가장 처음 시도한 방법은 모든 구간 $[l, r]$에 대하여 팰린드롬인가를 확인할 수 있는 배열 $chk[l][r]$을 만들었다. 이후 행렬 곱셈 순서 문제와 비슷한 아이디어로 문자열이 두 개의 부분 문자열로 나누어질 때에 대해 생각해봤다.

    즉 $[l, i]$와 $[i + 1, r]$ (단, $l \leq i < r$)이 팰린드롬인지 확인하고 팰린드롬이라면 1을 더해주고, 팰린드롬이 아니라면 넘어가는 방법을 이용했다.

    구현하면서 꽤 괜찮은 아이디어라고 생각을 하고 제출해봤지만 TLE를 받았다.

    TLE를 받은 후 두 부분 문자열 $S_{l...i}$와 $s_{i+1...r}$ 중 적어도 하나가 팰린드롬인 경우만으로 조건을 약간 바꿔봤지만 역시나 길이 2500의 랜덤 문자열을 판단하는데에는 제법 오랜 시간이 걸렸다.

    실제로 이 방법은 모든 구간$[l, r]$에 대해 $d[l][r]$을 구해야하고, 각각의 답을 구하기 위해 $r - l$번 연산을 하기 때문에 $O(n^3)$에 문제를 해결하기 때문에 TLE를 받을 수 밖에 없다.

    그렇다면 조금 더 효율적으로 답을 구하는 방법에 대해 고민해볼 필요가 있다.

     

    사실 반쯤 감긴 눈으로 의자에 앉아 고민을 하다보니 번뜩이는 아이디어가 떠올랐다. 결론부터 얘기하자면 BFS를 이용하는 방법이다.

    먼저 chk배열을 통해 모든 구간 $[l, r]$에 대하여 팰린드롬인가를 확인하는 것은 동일하다. 하지만 이 preprocess 과정에서 하나의 작업을 더 해준다. 인접 리스트를 만들어 node $l$과 node $r + 1$이 연결된 방향 그래프를 만들어 주는 것이다. 특정 구간이 팰린드롬이라면 해당 구간을 부분 문자열로 떼어낼 수 있으므로 간선의 가중치는 1이다. 이렇게 만들어준 인접 리스트를 이용하여 시작 node인 0번 node부터 마지막 노드인 $|S|$까지 BFS를 이용하여 최단 거리를 구해준다면 문제의 조건을 만족하는 정답을 구할 수 있다.
    사실 MLE가 나지 않을까 내심 걱정했지만 의외로 통과했다. 

    더보기
    #include<bits/stdc++.h>
    using namespace std;
    int chk[2525][2525];
    vector<int> adj[2525];
    char msg[2525];
    int d[2525];
    bool pal(int l, int r) {
    	if (l >= r) return 1;
    	int& res = chk[l][r];
    	if (~res) return res;
    	res = 0;
    	res = pal(l + 1, r - 1) && msg[l] == msg[r];
    	if (res)
    		adj[l].push_back(r + 1);
    
    	pal(l + 1, r);
    	pal(l, r - 1);
    	return res;
    }
    int main() {
    	memset(chk, -1, sizeof(chk));
    	scanf("%s", msg);
    	int sz = 0;
    	for (; msg[sz]; ++sz) {
    		chk[sz][sz] = 1;
    		adj[sz].push_back(sz + 1);
    	}
    	pal(0, sz - 1);
    	queue<int> q;
    	q.push(0);
    	d[0] = 1;
    	while (q.size()) {
    		int cur = q.front(); q.pop();
    		if (cur == sz) {
    			printf("%d", d[cur] - 1);
    			return 0;
    		}
    		for (int i = 0; i < adj[cur].size(); ++i) {
    			int nx = adj[cur][i];
    			if (d[nx] == 0) {
    				d[nx] = d[cur] + 1;
    				q.push(nx);
    			}
    		}
    	}
    }

    Preprocess를 단순한 방법이 아닌 부분 문자열이 팰린드롬인지 $O(n)$에 확인할 수 있는 Manacher's Algorithm을 이용한다면 조금 더 실행속도를 빠르게 만들 수 있으리라 생각한다. 하지만 잘 모르는 알고리즘이기에 나중에 공부해서 다시 풀어볼까 생각중이다.

    문제의 알고리즘 분류가 dp로 되어있는데 사실 dp로는 어떻게 접근해야 할 지 잘 모르겠다.

    '공부(Archive) > BOJ' 카테고리의 다른 글

    BOJ 1168: 요세푸스 문제 2  (0) 2020.12.20
    BOJ 3025: 돌 던지기 (미해결)  (0) 2020.05.13
    BOJ 3079: 입국심사  (0) 2020.05.06
    BOJ 10775: 공항  (0) 2020.05.06
    BOJ 2613: 숫자구슬  (0) 2020.02.20

    댓글

Designed by Tistory.