아버지는 원형 정원을 M개, 직선형 정원을 K개 가지고 있다. 각 정원에는 사이프러스 나무가 일정 간격으로 심겨져 있고, 서로 이웃한 두 사이프러스 나무 사이에는 항상 올리브 나무가 한 그루씩 심겨져 있다. 아버지는 아들에게 사이프러스 나무를 Q개 선택하면, 그 Q개의 사이프러스 나무와 그 나무들 사이에 있는 올리브 나무도 전부 준다는 말을 유언으로 남겼다. 원형 정원과 직선형 정원에서 연속한 사이프러스 구간들을 골라서 사이프러스 수가 정확히 Q가 되도록 해야 하고, 이때 얻을 수 있는 올리브 나무의 최대 개수를 구하는 것이 목표이다.
직관적으로 구현했더니 좀 복잡해졌다... 일단 정리해보자.
따라서, 같은 수의 사이프러스를 쓴다고 할 때, 원형 정원을 선택하는 것이 직선 정원보다 무조건 이득이다.
이렇게 풀면 답이 짜잔 하고 나온다.

처음에 원형 정원을 배낭DP로 선택하고, 남은 건 대해 직선형 정원에서만 선택했더니 79%에서 WA를 받았다. 그래서 역추적 과정을 추가해 선택하지 않은 원형 정원까지 후보에 추가해서 선택했더니 AC가 나왔다. 분명 풀 때 DP, 그리디, 배낭 문제, 역추적, 정렬을 썼는데 다른 사람들 기여를 보니 역추적, 정렬을 쓴 사람은 아무도 없었다...
다른 사람들과 런타임도 많이 차이나길래 어디에서 그렇게 차이가 나나 하고 몇 명의 코드를 봤더니
이게 핵심 로직이었다. 구체적인 알고리즘은 아래와 같다.
아이디어가 좀 어렵다... 내 풀이든 다른 사람 풀이든 G2치곤 어려워 보였다.
# 백준 2335
import io
input = io.BufferedReader(io.FileIO(0), 1<<18).readline
def solve(Q, M, tree, line):
# DP로 나무 제한 Q를 넘지 않는 최대 나무 수 계산
DP = [0] * (Q+1)
prevSum = [-1] * (Q+1)
prevIdx = [-1] * (Q+1)
DP[0] = 1
for i in range(M):
curW = tree[i]
for j in range(Q, curW-1, -1):
if DP[j] == 0 and DP[j-curW] == 1:
DP[j] = 1
prevSum[j] = j-curW
prevIdx[j] = i
# 정원을 선택하고 남은 나무의 수 계산
olive = 0
leftTree = Q
for i in range(Q, -1, -1):
if DP[i] == 1:
olive += i
leftTree -= i
break
# 선택한 정원 역추적
use = set()
cur = Q - leftTree
while cur != 0:
curI = prevIdx[cur]
use.add(curI)
cur = prevSum[cur]
# 선택되지 않은 정원 이랑에 추가
for i in range(M):
if i not in use:
line.append(tree[i])
# 남은 정원과 이랑에서 그리디하게 선택
if leftTree > 0:
line.sort(reverse=True)
for curL in line:
if curL < leftTree:
leftTree -= curL
olive += curL-1
else:
olive += leftTree-1
break
return olive
def main():
Q, M, K = map(int, input().split())
tree = list(map(int, input().split()))
line = list(map(int, input().split()))
print(solve(Q, M, tree, line))
main()