문제
문자열의 집합 두 개 $S_C, S_N$가 주어진다. 각각 크기는 $C$, $N$이다. $Q$개의 문자열 쿼리를 받아서 그 문자열 $q$를 두 부분문자열 $c\in S_C$와 $n\in S_N$으로 분할 할 수 있는지 출력하는 문제이다.
접근
시간 제한 3초에 메모리 1024MB로 제한이 아주 넉넉하다. 백준 온라인 저지는 Python의 경우 여기에다가 추가로 메모리와 시간 제한을 늘려주기 때문에 11초 안에만 풀면 된다🤣
그래서… 예전에 Trie를 알기 전에는 딕셔너리를 사용하여 야매로 풀려고도 했었다.ㅋㅋfrom sys import stdin
c, n = map(int, stdin.readline().split())
colors = {stdin.readline().strip() for _ in range(c)}
names = {stdin.readline().strip() for _ in range(n)}
queries = [stdin.readline().strip() for _ in range(int(stdin.readline().strip()))]
for query in queries:
flag = False
for i in range(1, len(query) - 1):
if query[:i] in colors and query[i:] in names:
flag = True
break
print('Yes' if flag else 'No')
어찌보면 당연하게도 문자열 슬라이싱을 너무 많이 하는 바람에 시간초과
를 받았고, 결국 Trie를 공부해서 적용했다.
Trie의 경우 뭐 복잡한 것이 없다. 트리를 만드는데 타고 내려가면서 Prefix를 얻도록 잘 설계하자는 것이 끝이다. 딱 3개의 정보만 관리해주면 된다. 각 노드에 저장할 내용, 그리고 다음 노드가 무엇인지(트리 구조가 되도록), 이 노드에서 끝나는 문자열이 있는지이다.
풀이
나이브하게 딕셔너리를 만들고 잘라보면서 찾자! 는 생각은 시간에 걸렸고, Trie를 적용하기로 했으니 이제 풀이를 구체화해 보자. 쿼리가 들어오면 앞에서부터 한 글자씩 진행하면서 $S_C$로 구성한 Trie를 탐색한다…
그런데 그러면 뒷부분은 어떻게 검색해야 할까? $S_N$으로도 Trie를 구성해서 탐색해야 하나? 생각해보면 Prefix를 그대로 두면서 뒷부분을 붙인 경우를 탐색할때 빠른 것이 이 문제에서 사용하는 Trie의 장점이다. 헌데 뒷부분은 계속 Prefix가 바뀌므로 매번 새롭게 슬라이싱으로 복사해서 사용해야 하고, 그러면 사실상 딕셔너리를 사용하는 것과 다름이 없다.
해서! 나는 그냥 $S_N$에 대해서는 딕셔너리를 사용해버렸다. Best solution을 생각해보자면 아마 문자열을 뒤집어서 Trie를 만들고, 쿼리도 뒤집어서 처리하는 방법으로 두 문자열 집합을 모두 Trie구조로 할 수도 있을 것이다. 그러나 앞서 말했듯 Python은 11초라는 무지막지한 시간 제한이 있기 때문에, 부담없이 복사해버린 다음 딕셔너리에 있는지 확인했다. Trie를 두개 만드는 것보다 이쪽이 간단하다.
Code
링크# https://www.acmicpc.net/problem/19585
# Trie
from sys import stdin
c, n = map(int, stdin.readline().split())
container = ['']
nxt = [[]]
end = [False]
def add(word):
pos = i = 0
while i < len(word):
for next_pos in nxt[pos]:
# if next letter matches: move to that node
if container[next_pos] == word[i]:
pos = next_pos
i += 1
break
# if nothing matches: insert new node
else:
nxt[pos].append(len(container))
nxt.append([])
end.append(False)
pos = len(container)
container.append(word[i])
i += 1
end[pos] = True
# search in trie and yield index whenever available
def find(word):
pos = i = 0
while i < len(word) - 1:
for next_pos in nxt[pos]:
# if next letter matches: move to that node
if container[next_pos] == word[i]:
pos = next_pos
i += 1
if end[pos]:
yield i
break
# if nothing matches: end of iteration
else:
return
for _ in range(c):
color = stdin.readline().strip()
add(color)
names = {stdin.readline().strip() for _ in range(n)}
for _ in range(int(stdin.readline().strip())):
query = stdin.readline().strip()
for i in find(query):
# print(query[i:])
if query[i:] in names:
print('Yes')
break
else:
print('No')
여담
슬슬 코테에 안나오는 알고리즘이라 그런가 솔브닥 class 6 문제임에도 Python으로 푼 사람이 적다. 아마 뒷부분까지 잘 처리해주면 시간을 줄일 수 있을 텐데, 사람이 적어서 제출한 뒤에 1등을 먹어버린터라 귀찮아져버렸다😅😅😅