문제
수열 A가 주어졌을 때, 그 수열의 증가하는 부분 수열 중에서 합이 가장 큰 것을 구하는 프로그램을 작성하시오.
예를 들어, 수열 A = {1, 100, 2, 50, 60, 3, 5, 6, 7, 8} 인 경우에 합이 가장 큰 증가하는 부분 수열은 A = {1, 100, 2, 50, 60, 3, 5, 6, 7, 8} 이고, 합은 113이다.
입력
첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000)이 주어진다.
둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000)
출력
첫째 줄에 수열 A의 합이 가장 큰 증가하는 부분 수열의 합을 출력한다.
예제 입력 1
10
1 100 2 50 60 3 5 6 7 8
예제 출력 1
113
출처
- 문제를 만든 사람: baekjoon
- 데이터를 추가한 사람: gee308, gomyk12, mohana9
비슷한 문제
- 11053번. 가장 긴 증가하는 부분 수열
- 11054번. 가장 긴 바이토닉 부분 수열
- 11722번. 가장 긴 감소하는 부분 수열
- 12015번. 가장 긴 증가하는 부분 수열 2
- 12738번. 가장 긴 증가하는 부분 수열 3
- 14002번. 가장 긴 증가하는 부분 수열 4
- 14003번. 가장 긴 증가하는 부분 수열 5
알고리즘 분류
- 다이나믹 프로그래밍
풀이
이제 좀 심화 DP 느낌이 날듯말듯 한다. 이것은 단순한 길이 재기보다 더 까다로운 조건을 따른다. 그 까닭은 명확하다. 이번에는 실제 원본 수열에서 값을 현명하게 뽑아서 DP 배열에 어떻게 넣어 주느냐가 중요하기 때문이다. 즉 DP 배열과 원본 수열을 잘 생각하면서 과거와 현재의 분기점을 만드는 방식을 사용한다고 보면 된다.
반복문 안의 이 부분을 얼마나 정밀하게 보는지가 핵심이다.
if(A[i] < A[k]) DP[k] = MAX(DP[k], A[k] + DP[i]);
이것은 결과적으로 현재 위치에 대해 누적된 수들의 합이, 현재 것과 현재 것 이전을 색인하다 찾은 합을 더한 것보다 작느냐? 그렇다면 합을 그것으로 갱신하라는 논리를 따르게 된다. 이러한 논리는 직관적이지만 처음 볼 때 생각하기 어렵다. 물론 백준의 문제 난이도 티어를 올릴 정도는 아니지만 이제서야 실전 DP에 조금이나마 가까운 문제를 푼다는 감각이다.
이제 실전적인 풀이를 보면서 다시 한 번 정리해 볼 필요가 있다.
코드
#include <stdio.h>
#include <stdlib.h>
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
int main() {
int N;
scanf("%d", &N);
int *A = calloc(N, sizeof(int));
int *DP = calloc(N, sizeof(int));
for(int i = 0; i < N; i++) {
// if you prefer unary,
//
// scanf("%d", &A[i]);
scanf("%d", A + i);
}
int result = 0;
for(int k = 0; k < N; k++) {
DP[k] = A[k];
for(int i = 0; i < k; i++) {
// here's the main point.
if(A[i] < A[k]) DP[k] = MAX(DP[k], A[k] + DP[i]);
}
result = MAX(result, DP[k]);
}
printf("%d", result);
free(A);
free(DP);
return 0;
}