N개의 단어가 주어진다.
26개의 알파벳 중 K개의 알파벳만을 가르쳐서 최대한 많은 단어를 읽을 수 있도록 해야한다.
모든 단어의 시작은 anta
, 끝은 tica
로 끝나므로, [a, c, i, n, t]
5개의 단어는 반드시 가르쳐야 한다.
N=3 K=6
antarctica
antahellotica
antacartica
a, c, i, n, t
에 더해서 r
을 가르친다면 antarctica
, antacartica
2개를 읽을 수 있다.
나머지 경우의 수는 아무 단어도 읽을 수 없다.
따라서 답은 2가 된다.
(실제로는 z, y, x ... , d, c, b, a 의 역순 비트이다.)
위 그림과 같이 단어를 비트로 변환하여 가르친 알파벳과 &
연산을 함으로써 단어를 읽을 수 있는지 없는지 여부를 판단할 수 있다.
# 단어를 비트로 변환하는 함수
def word2bit(word):
bit = 0
for char in word:
bit = bit | (1 << ord(char) - ord('a'))
return bit
예를 들어 acint
라는 단어가 주어졌을 때
a -> 0
, c -> 2
, i -> 8
, n -> 13
, t -> 19
로 각각 변환하여 left shift 연산을 통해 비트를 만들어낸다.
acint는 000000010000010000100000101 = 532741
로 변환된다.
이제 알파벳을 가르칠 수 있는 경우의 수(조합)을 구해야 한다.
파이썬에서는 itertools
라이브러리의 combinations
함수를 사용해 쉽게 구할 수 있다.
# 가르칠 수 있는 알파벳 조합
base_bit = word2bit('acint')
alphabet = [1 << i for i in range(26) if not (base_bit & 1 << i)]
answer = 0
for combination in combinations(alphabet, K-5):
know_bit = sum(combination) | base_bit
a, c, i, n, t
는 반드시 가르쳐야하는 단어 조합이므로, 5개의 알파벳을 제외한 나머지 단어에서 조합을 구한다.
0 ~ 25까지의 범위에서 반복문을 돌고, 반드시 가르쳐야 하는 단어 조합(base_bit
)이 아닌 알파벳의 비트를 alphabet
리스트에 추가해준다.
구해진 알파벳 리스트를 출력하면 다음과 같다.
[2, 8, 16, 32, 64, 128, 512, 1024, 2048, 4096, 16384, 32768, 65536, 131072, 262144, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432]
['0b10', '0b1000', '0b10000', '0b100000', '0b1000000', '0b10000000', '0b1000000000', '0b10000000000', '0b100000000000', '0b1000000000000', '0b100000000000000', '0b1000000000000000', '0b10000000000000000', '0b100000000000000000', '0b1000000000000000000', '0b100000000000000000000', '0b1000000000000000000000', '0b10000000000000000000000', '0b100000000000000000000000', '0b1000000000000000000000000', '0b10000000000000000000000000']
이어서 combinations
함수를 사용해 모든 경우의 수에 대해 반복문을 수행한다.
이미 이미 알고있는 단어를 합쳐야 하므로, |
비트 연산을 통해 하나의 비트로 만든다.
count = 0
for bit in bits:
if bit & know_bit == bit:
count += 1
bit
는 주어진 단어, know_bit
는 읽을 수 있는 알파벳을 의미한다.
만약 단어에 있는 알파벳을 모두 읽을 수 있다면, &
연산 결과가 변하지 않아야 한다.
만약 읽을 수 없는 알파벳이 있다면 &
연산으로 해당 비트 부분이 0이 되어 값이 변할 것이다.
위와 같은 원리로 읽을 수 있는 단어와 읽을 수 없는 단어를 구분할 수 있다.
단어를 읽을 수 있다면 count를 1 증가시키고, 모든 경우에 수에 대해 count의 최대값을 구하면 답을 구할 수 있다.
import sys
from itertools import combinations
input = sys.stdin.readline
def word2bit(word):
bit = 0
for char in word:
bit = bit | (1 << ord(char) - ord('a'))
return bit
N, K = map(int, input().split())
words = [input().rstrip() for _ in range(N)]
bits = list(map(word2bit, words))
base_bit = word2bit('antic')
if K < 5:
print(0)
else:
alphabet = [1 << i for i in range(26) if not (base_bit & 1 << i)]
answer = 0
for combination in combinations(alphabet, K-5):
know_bit = sum(combination) | base_bit
count = 0
for bit in bits:
if bit & know_bit == bit:
count += 1
answer = max(answer, count)
print(answer)
파이썬이 실행속도가 다른 언어에 비해 느린편이라 이런 브루트포스 문제가 까다로운 것 같다.
조금이라도 정해보다 실행속도가 느려지는 부분이 있다면 바로 시간초과가 발생하는 경우가 잦다.
실제로도 문제 난이도(티어)에 비해 유독 브루트포스 유형의 문제를 풀 때 시간 초과가 자주 발생하고 어렵다고 느꼈다.
이번 경우에도 비트마스크가 익숙하지 않아서 그냥 다른 방법으로 풀어서 pypy3에서만 겨우 통과했다가 다른 사람의 코드를 참고해 다시 풀어 본 것이다.
많이 연습해서 브루트포스 문제에서 시간복잡도를 줄이는 방법을 익혀야 할 것 같다.