https://www.acmicpc.net/problem/11478
처음에 봤을 때 부분 문자열의 조합을 이중반복문을 통해 구하고 이를 set 자료형을 통해 하나씩 넣고 마지막에 그 크기를 출력해주는 것으로 생각했다. 이렇게 풀어도 정답이 된다.
정답코드1(반복문 사용)
#include <iostream>
#include <set>
using namespace std;
set<string> s;
int main()
{
string word;
cin >> word;
//부분 문자열 시작위치
for (int i = 0; i < word.size(); i++)
{
string w = "";
for (int j = i; j < word.size(); j++) //부분 문자열 길이
{
w += word[j];
s.insert(w);
}
}
cout << s.size() << endl;
}
하지만 더 최적화해보자면 접미사 배열 (Suffix Array) 이라는 것을 사용해서 풀면 된다.
이때 접미사 배열은 문자열 s의 모든 접미사를 사전 순으로 정렬한 것으로 각 접미사가 문자열에서 시작하는 인덱스를 기준으로 정렬 한다. 예시 입력을 바탕으로 보자면
s의 접미사 목록:
- s[0:] = "ababc"
- s[1:] = "babc"
- s[2:] = "abc"
- s[3:] = "bc"
- s[4:] = "c"
이런식으로 접미사가 만들어진다.
여기서 중복되는 부분 문자열을 제거하기 위해 접미사 배열을 활용한 LCP 배열이라는 것을 사용하게 되는데 이는 은 접미사 배열의 인접한 접미사들 간에 겹치는 접두사의 길이를 저장한 배열이다.
인접한 접미사들 간의 공통 접두사:
- "ababc"와 "abc"의 공통 접두사: "ab" → 길이 2
- "abc"와 "babc"의 공통 접두사: 없음 → 길이 0
- "babc"와 "bc"의 공통 접두사: "b" → 길이 1
- "bc"와 "c"의 공통 접두사: 없음 → 길이 0
이렇게 되면 LCP배열은 [2, 0, 1, 0] 이런식으로 만들어 진다.
이제 중복되는 것을 포함한 전체 부분 문자열의 개수를 구해준 다음 중복되는 수를 빼주면 된다.
부분 문자열의 개수는 n(n+1) /2 로 계산할 수 있다.
이를 코드로 구현하면 다음과 같다.
정답코드2(접미사 배열활용)
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
// 접미사 배열 구하기
vector<int> build_suffix_array(const string& s) {
int n = s.size();
vector<int> suffix_array(n), rank(n), temp(n);
// 초기 순위 설정 (문자의 아스키 값에 따른 정렬)
for (int i = 0; i < n; i++) {
suffix_array[i] = i;
rank[i] = s[i];
}
// 접미사 길이 1, 2, 4, 8...씩 늘려가며 정렬
for (int len = 1; len < n; len *= 2) {
auto cmp = [&](int i, int j) {
if (rank[i] != rank[j]) return rank[i] < rank[j];
int ri = (i + len < n) ? rank[i + len] : -1;
int rj = (j + len < n) ? rank[j + len] : -1;
return ri < rj;
};
sort(suffix_array.begin(), suffix_array.end(), cmp);
// 임시 순위 갱신
temp[suffix_array[0]] = 0;
for (int i = 1; i < n; i++) {
temp[suffix_array[i]] = temp[suffix_array[i - 1]] + cmp(suffix_array[i - 1], suffix_array[i]);
}
rank = temp;
}
return suffix_array;
}
// LCP 배열 구하기
vector<int> build_lcp_array(const string& s, const vector<int>& suffix_array) {
int n = s.size();
vector<int> rank(n), lcp(n);
for (int i = 0; i < n; i++) {
rank[suffix_array[i]] = i;
}
int h = 0;
for (int i = 0; i < n; i++) {
if (rank[i] > 0) {
int j = suffix_array[rank[i] - 1];
while (i + h < n && j + h < n && s[i + h] == s[j + h]) {
h++;
}
lcp[rank[i]] = h;
if (h > 0) h--;
}
}
return lcp;
}
// 서로 다른 부분 문자열 개수 구하기
int count_unique_substrings(const string& s) {
int n = s.size();
// 접미사 배열과 LCP 배열 생성
vector<int> suffix_array = build_suffix_array(s);
vector<int> lcp = build_lcp_array(s, suffix_array);
int total_substrings = n * (n + 1) / 2; // 모든 부분 문자열의 개수
int lcp_sum = 0; // LCP들의 합
for (int i = 1; i < n; i++) {
lcp_sum += lcp[i]; // LCP 값의 합
}
return total_substrings - lcp_sum; // 전체 부분 문자열에서 중복된 부분을 빼기
}
int main() {
string s;
cin >> s;
int result = count_unique_substrings(s);
cout << result << endl;
return 0;
}
확실히 공간,시간 복잡도가 확연히 줄어든 모습을 볼 수있다. 이론은 이해할 수 있었지만 이를 구현하기는 어렵지만 최적화가 필요할 때는 이 방법을 활용하면 좋을 것같다.
'코딩테스트 > 백준' 카테고리의 다른 글
[백준][C++]4949번. 균형잡힌 세상 (2) | 2024.10.02 |
---|---|
[백준][C++]9012번. 괄호 (0) | 2024.10.02 |
[백준][C++]1269번. 대칭 차집합 (6) | 2024.09.30 |
[백준][C++]1764번. 듣보잡 (0) | 2024.09.30 |
[백준][C++]1620번. 나는야 포켓몬 마스터 이다솜 (3) | 2024.09.27 |