[PS] BOJ 16987 / 계란으로 계란치기
문제 링크: https://www.acmicpc.net/problem/16987
계란 프라이가 먹고 싶은 밤입니다. 백트래킹으로 가능한 모든 경우를 탐색해 최댓값을 찾으면 됩니다.
풀이
문제 조건 분석하기
계란으로 계란을 치는 과정은 다음과 같습니다.
- 가장 왼쪽의 계란을 든다.
- 손에 들고 있는 계란으로 깨지지 않은 다른 계란 중에서 하나를 친다. 단, 손에 든 계란이 깨졌거나 깨지지 않은 다른 계란이 없으면 치지 않고 넘어간다. 이후 손에 든 계란을 원래 자리에 내려놓고 3번 과정을 진행한다.
- 가장 최근에 든 계란의 한 칸 오른쪽 계란을 손에 들고 2번 과정을 다시 진행한다. 단, 가장 최근에 든 계란이 가장 오른쪽에 위치한 계란일 경우 계란을 치는 과정을 종료한다.
0번 계란부터 $N-1$번 계란까지 하나씩 들면서 다른 계란들 중 깨지지 않은 것을 하나 골라 치는 과정을 반복해주면 됩니다.
계란을 치다
이때, "계란을 친다" 라는 과정은 다음과 같이 정의됩니다.
두 계란 $a, b$에 대해, 각각의 내구도를 $D_a, D_b$라 하고 무게를 $W_a, W_b$입니다.
계란 $a$로 $b$를 치면, 각각의 무게는 다음과 같이 변합니다:
$D_a = D_a - W_b$, $D_b = D_b - W_a$
백트래킹 유망성 판단하기
백트래킹이란, DFS 등의 그래프 탐색을 진행하되 유망하지 않은 분기는 즉시 탐색을 중단하는 방법입니다.
따라서, 각 분기가 유망한지 아닌지를 판단하기 위한 조건이 필요합니다. 이 문제의 경우 다음의 경우는 더 탐색하지 않아도 됩니다:
들고 있는 계란 $h$와 치려는 계란 $i$에 대해,
- $i$가 이미 깨진 상태일 때
- $i = h$일 때 (자기 자신을 칠 수 없다.)
백트래킹
이제 백트래킹을 구현하면 됩니다.
구현 시 주의할 점은 다음과 같습니다.
- 계란의 남은 내구도가 0 이하일 때 깨진 상태입니다.
- 계란은 왼쪽에서부터 순서대로 하나씩 손에 듭니다.
- 손에 들고 있는 계란으로 깨지지 않은 다른 계란을 1 개만 칩니다.
- 손에 들고 있는 계란이 이미 깨져 있거나, 다른 모든 계란이 깨져 있는 경우는 치지 않고 넘어갑니다.
- 이외의 경우에는 반드시 계란을 1개 쳐야 합니다.
EGGS = list(list(map(int, input().split())) for _ in range(N))
# EGGS = [DURABILITY, WEIGHT]
DURABILITY = 0
WEIGHT = 1
def dfs(holding):
if holding == N: # 가장 오른쪽에 위치한 계란을 들 때 종료
cnt = 0
for egg in EGGS:
if egg[DURABILITY] <= 0:
cnt += 1
return cnt
if EGGS[holding][DURABILITY] <= 0:
return dfs(holding + 1)
cnt = 0
hit = False
# 다른 계란을 치는 경우
for i in range(N):
cur_durability, cur_weight = EGGS[holding]
other_durability, other_weight = EGGS[i]
# 들고있는 계란을 치는 경우 / 치려는 계란이 이미 깨진 경우는 DFS를 진행하지 않는다.
if i == holding or other_durability <= 0:
continue
EGGS[holding][DURABILITY] -= other_weight
EGGS[i][DURABILITY] -= cur_weight
hit = True
res = dfs(holding + 1)
if res > cnt:
cnt = res
EGGS[holding][DURABILITY] = cur_durability
EGGS[i][DURABILITY] = other_durability
# 아무것도 깨지 않고 다음 계란으로 넘어갔을 경우
if not hit:
res = dfs(holding + 1)
if res > cnt:
cnt = res
return cnt백트래킹
전체 코드
input = open(0).readline
N = int(input())
EGGS = list(list(map(int, input().split())) for _ in range(N))
# EGGS = [DURABILITY, WEIGHT]
DURABILITY = 0
WEIGHT = 1
def dfs(holding):
if holding == N: # 가장 오른쪽에 위치한 계란을 들 때 종료
cnt = 0
for egg in EGGS:
if egg[DURABILITY] <= 0:
cnt += 1
return cnt
if EGGS[holding][DURABILITY] <= 0:
return dfs(holding + 1)
cnt = 0
hit = False
# 다른 계란을 치는 경우
for i in range(N):
cur_durability, cur_weight = EGGS[holding]
other_durability, other_weight = EGGS[i]
# 들고있는 계란을 치는 경우 / 치려는 계란이 이미 깨진 경우는 DFS를 진행하지 않는다.
if i == holding or other_durability <= 0:
continue
EGGS[holding][DURABILITY] -= other_weight
EGGS[i][DURABILITY] -= cur_weight
hit = True
res = dfs(holding + 1)
if res > cnt:
cnt = res
EGGS[holding][DURABILITY] = cur_durability
EGGS[i][DURABILITY] = other_durability
# 아무것도 깨지 않고 다음 계란으로 넘어갔을 경우
if not hit:
res = dfs(holding + 1)
if res > cnt:
cnt = res
return cnt
print(dfs(0))solution.py