티스토리 뷰
문제 출처 : algospot.com :: FANMEETING
algospot.com :: FANMEETING
팬미팅 문제 정보 문제 가장 멤버가 많은 아이돌 그룹으로 기네스 북에 올라 있는 혼성 팝 그룹 하이퍼시니어가 데뷔 10주년 기념 팬 미팅을 개최했습니다. 팬 미팅의 한 순서로, 멤버들과 참가
algospot.com
1. 문제 해결을 위한 사고 과정
(1) 무식하게 풀기
- 팬의 수 N, 멤버의 수 M이라할 때 팬미팅 과정을 하나하나 시뮬레이션하는 방법
- 대략 O(M(N-M)) = O(NM_M^2)의 시간을 필요로 함
- 그러나 N과 M이 최대 200,000인 경우를 생각하면 제한 시간 안에 해결하기 어려움
(2) 곱셈으로 변형하기
1) 대략적인 생각
- 팬과 멤버들을 길이가 N과 M인 정수 배열로 생각하면 가능하지 않을까?
- 팬과 멤버에 대해서 남자는 1, 여자는 0으로 바꿔서 표현
- 각 배열의 자리에 대한 곱의 값이 1이면 남성 팬과 남성 멤버가 악수를 하는 것으로 간주
- 반대로 0(남-여, 여-여)이면 포옹을 하는 것으로 간주
- 모두 0인 결과가 나오면 경우의 수를 누적할 수 있다!
- 또한, 자리 올림이 발생하지 않기 때문에 normalize() 생략 가능
2) 구체화
- 문제의 조건에서 팬의 수는 항상 멤버의 수보다 커야 한다.
- 만일 멤버의 수가 3명이고 팬의 수가 6명인 경우를 생각해보자
- 각 자리에 1과 0으로 성별 정보를 담은 멤버에 대한 길이 3인 배열 A와 팬의 대한 길이 6인 배열 B로 표현할 수 있다.
- 이를 카라츠바 알고리즘의 경우 처럼 곱셈으로 표현하면 Ci에 대해 다음과 같이 표현 가능하다.
- Ci는 A의 연속된 값(0, 1, 2)과 B의 i부터 A크기 만큼의 연속된 값을 역순으로(i, i-1, i-2) 곱한 형태를 띄게 된다.
- 그러나 문제를 해결하기 위해서는 A와 B는 같은 순서로 비교해야 한다.
- 이는 B의 순서를 뒤집어서 곱하면 Ci에 대해서 A와 B를 같은 순서로 곱한 값을 얻어 낼 수 있다.
- 이를 응용하여 코드를 구현하면 문제를 해결할 수 있다.
2. 카라츠바 알고리즘을 이용한 구현
(1) 코드 1 : 두 큰 수를 곱하는 O(n^2) 시간 알고리즘 변형
def multiply(a, b):
# c = [0] * (len(a) + len(b) + 1)
c = [0] * (len(a) + len(b) - 1)
for i in range(len(a)):
for j in range(len(b)):
c[i+j] += a[i] * b[j]
# normalize(c) 사용하지 않음!
return c
- 기존 예제의 코드에서 자리 올림(normalize())을 사용하지 않는다.
- c[i+j]에 단순히 a[i]과 b[j]를 곱한 결과를 저장한다. (c[i+j]에 저장하는 이유는 이전 예제에서 확인)
(2) 코드 2 : 카라츠바 알고리즘
def addTo(a, b, k):
# 자리수 맞추기
newsize = max(len(a), len(b) + k)
# 동일한 자리수가 되도록 0추가
while (len(a) != newsize):
a.append(0)
while (len(b) != newsize):
b.append(0)
# b의 최소 자리수부터 부터 더함
for i in range(k, newsize):
a[i] = a[i] + b[i - k]
# normalize(a) 사용 안함!
def subFrom(a, b):
for i in range(len(b)):
a[i] -= b[i]
# normalize(a) 사용 안함!
def karatsuba(a, b):
an = len(a)
bn = len(b)
# a가 b보다 짧은 경우 교체
if an < bn:
return karatsuba(b, a)
# 기저 사례 1 : a나 b가 빈 리스트인 경우
if an == 0 or bn == 0:
return []
# 기저 사례 2 : a가 비교적 짧은 경우 O(n^2) 곱셈으로 변경
if an <= 2:
return multiply(a, b)
half = an // 2
a0 = a[:half]
a1 = a[half:]
b0 = b[:min(bn, half)]
b1 = b[min(bn, half):]
z0 = karatsuba(a0, b0)
z2 = karatsuba(a1, b1)
addTo(a0, a1, 0)
addTo(b0, b1, 0)
z1 = karatsuba(a0, b0)
subFrom(z1, z0)
subFrom(z1, z2)
result = []
addTo(result, z0, 0)
addTo(result, z1, half)
addTo(result, z2, 2*half)
return result
(3) 코드 3 : 모두 포옹하는 경우의 수 구하기
def hug(members, fans):
n = len(members)
m = len(fans)
a = [0] * n
b = [0] * m
for i in range(n):
if members[i] == 'M':
a[i] = 1
for i in range(m):
if fans[i] == 'M':
b[m-1-i] = 1
c = karatsuba(a, b)
allHugs = 0
for i in range(n-1, m):
if c[i] == 0:
allHugs += 1
return allHugs
- Ci에 대해서 0이 나오는 횟수가 바로 정답이다.
- 팬에 대한 배열인 b에 대해서 b[m-i-1]부터 배열을 채워 나가면 순서를 뒤집는 형태가 된다.
- 따라서 결과적으로 A와 B는 같은 순서대로 값을 곱하고 그 결과를 확인할 수 있다.
3. 시간 복잡도 분석
시간 복잡도는 두 수의 곱셈에 의해 좌우되므로 카라츠바 알고리즘과 동일한 O(n^log3)이다.
4. 후기
문제를 처음 접했을 때 풀이를 보기 전 까지는 감히 카라츠바의 알고리즘을 응용한다는 생각을 할 수가 없었다. 이 문제를 만든 사람이나 해결한 사람이나 정말 미친 사람이 아닐까 하는 생각이 들었다. 항상 문제를 보면서 느끼는 건데 문제 풀이는 이해가 되는데 어떻게 해결 방법을 생각할 수 있을까 하는 의구심이 생긴다. 아마 풀이를 보지 않는다면 나는 몇 년이 지나도록 풀지 못했을 것 같다.
그래도 이번 문제를 통해 이전 예제에서 두루 뭉실하게 넘어갔던 곱셉 방식을 카라츠바 알고리즘으로 옮기는 과정의 일부분을 명쾌하게 이해할 수 있었다. 역시 단순히 읽고 넘어가기 보다는 손으로 공책에 따라 쓰면서 그 과정을 이해하면 보다 명확하게 머릿속에 들어온다. 사실 배열 하나를 뒤집는 이유를 이해하지 못해서 거의 한 시간동안 머리를 싸매고 고민했었다. 책에서는 내 기준으로 짧게 설명하고 넘어가는 부분이라 배열을 뒤집어야 하는 이유를 당장 이해하지 못 했었다. 그러나 차근차근 그 과정을 손으로 적어보고 Ci에 대한 값이 어떻게 들어가야 문제가 해결되는지를 생각해보니 곧장 이해할 수 있었다.
LeeSeok-Jun/Algorithmic_Problem_Soving_Strategies
프로그래밍 대회에서 배우는 알고리즘 문제해결 전략. Contribute to LeeSeok-Jun/Algorithmic_Problem_Soving_Strategies development by creating an account on GitHub.
github.com
'프로그래밍 대회에서 배우는 알고리즘 문제해결 전략 > 7장_분할 정복' 카테고리의 다른 글
7.4 문제 : 울타리 잘라내기(Python) (0) | 2021.03.19 |
---|---|
7.2 문제 : 쿼드 트리 뒤집기(Python) (0) | 2021.03.19 |
7.1 분할 정복_도입(2)_카라츠바의 빠른 곱셈 알고리즘(Python) (0) | 2021.03.16 |
7.1 분할 정복_도입(1) (0) | 2021.03.15 |
- Total
- Today
- Yesterday
- 난이도:상
- 구현
- k번째 답 계산하기
- 그리디
- 파이썬
- 종만북
- 비트 마스크
- 카카오
- 탐욕법
- 동적 계획법
- 난이도:하
- 프로그래머스
- 프로그래밍 대회에서 배우는 알고리즘 문제 해결 전략
- python
- 난이도:중
- 프로그래밍 대회에서 배우는 알고리즘 문제해결 전략
- 마르코프 연쇄
- 프로그래밍 대회에서 배우는 알고리즘 문제해결전략
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |