지난번에 정리한 백트레킹에 이어 한번 날잡고 제대로 공부해야겠다고 마음먹은 분할정복이다. 간혹가다가 분할정복 문제를 접하게 되면 그 매커니즘이 직관적으로는 이해되지만 100% 내것이다!라고 말하기엔 애매한 부분들이 좀 많은 것 같아, 제대로 개념 정리 후, 관련 문제들을 풀어보고자 한다. 그럼 우선 분할정복의 개념부터 알아보자!
주어진 문제를 둘 이상의 부분 문제로 나눈 뒤 각 문제에 대한 답을 재귀 호출을 이용해 계산, 각 부분 문제의 답으로부터 전체 문제의 답을 계산해내는 방식이다.
여기까지만 읽어보면 Dynamic Programming과 비슷하네?라는 생각이 들었다. 문제를 잘게 조개서, 작은 단위부터 분할해 접근한다는 점은 비슷하지만, DP와 분할정복에는 큰 차이가 있다.
그럼, 이어서 분할정복의 추가적인 특징들을 정리해보도록 하자.
이라고 한다. 어느정도 개념에 대한 대략적인 감이 잡혔으니 본격적으로 문제를 풀어보며 분할정복을 정복해보자!
첫번째 분할정복을 이용한 문제는 자연수 A의 B승을 C로 나눈 나머지를 구하는 문제이다. 처음에는 ㅇㅡㅇ? 하고
import math
a, b, c = map(int, input().split())
print(int(math.pow(a,b) % 12))
이렇게 간단한 문젠가?라고 생각했다가 보기좋게
런타임 에러가 떨어졌다. 이 문제를 해결하는 키는 바로 '분할정복'을 이용하는 것이다. 구체적인 매커니즘은 다음과 같다.
무식한 제곱의 연산 과정을 단순화 하기 위해선 나머지 분배 법칙과 지수법칙을 알아야 한다고 한다.
- 지수 법칙
A^m+n = A^m x A^n
- 나머지 분배 법칙
(AxB)%C = (A%C) *(B%C) % C
이 문제의 예제에 적용을 해보면
a = 10 , b = 11 , c = 12
10^11 % 12
= ((10^5)%12 x (10^5)%12 x 10)% 12
= ((10^2)%12 x (10^2)%12 x 10) %12 x (10^2)%12 x (10^2)%12 x 10) %12 x 10) %12
으로 곱셈을 계속해서 분할해 나갈 수 있다.
import sys
a,b,c = map(int,sys.stdin.readline().split())
def multi (a,n):
if n == 1:
return a%c
else:
tmp = multi(a,n//2)
if n %2 ==0:
return (tmp * tmp) % c
else:
return (tmp * tmp *a) %c
print(multi(a,b))
분할정복의 개념을 와닿게 이해할 수 있는 좋은 문제였지만.. 사실 위의 공식을 알고 있지 않다면, 거의 풀 수 없을 문제가 아닌가..라고 생각이 든다.
두번째 문제는
위 문제의 접근 방식은 다음과 같다.
1) 주어진 인풋에 대해 모두 같은 수로 되어 있는지 체크
1-1) 맞다면, 해당 종이의 수에 카운트 +1
1-2) 아니라면, 해당 종이를 9분할 후 다시 1번으로 돌아감.
import sys
n = int(sys.stdin.readline())
paper = [list(map(int, sys.stdin.readline().split())) for _ in range(n)]
neg = 0
neut = 0
pos = 0
#clip_paper의 파라미터로 시작 지점과 몇칸까지 볼지를 함께 받는 것이 포인트!
def clip_paper(x, y, n):
global neg, neut, pos
num_check = paper[x][y]
for i in range(x, x + n):
for j in range(y, y + n):
#같은 수로 이뤄져 있지 않은 경우 분할
if (paper[i][j] != num_check):
for k in range(3):
for l in range(3):
#시작 지점의 경우 k*n//3, l*n//3으로 범위를 조정하며, n값 역시 나누기3!
clip_paper(x + k * n // 3, y + l * n // 3, n // 3)
return
if (num_check == -1):
neg += 1
elif (num_check == 0):
neut += 1
else:
pos += 1
clip_paper(0, 0, n)
print(neg)
print(neut)
print(pos)
분할정복, 백트래킹 유형의 재귀를 사용한 유형은 정말 문제 자체를 직관적으로 이해하는 센스 + 어느정도 유형에 대한 풀이방식, 매커니즘을 간단하게는 외우고 있는게 중요한거 같다. 앞으로도 분할정복 문제는 조금 더 신경써서 풀고, 풀고 난 이후에도 기억에 남겨두어야겠다,,