백준 11054번 ‘가장 긴 바이토닉 부분 수열’은 어떤 peak까지 strictly increasing하고, 그 뒤 strictly decreasing하는 부분 수열의 최대 길이를 구한다. 각 index를 peak로 가정해 왼쪽에서 끝나는 LIS와 오른쪽으로 시작하는 감소 수열 길이를 더하면 된다.
두 DP 배열의 의미
increasing[i]:values[i]에서 끝나는 가장 긴 증가 부분 수열decreasing[i]:values[i]에서 시작하는 가장 긴 감소 부분 수열
두 값 모두 자기 자신을 포함하므로 1로 시작한다.
increasing[i]
= max(increasing[j] + 1), j < i and values[j] < values[i]
decreasing[i]
= max(decreasing[j] + 1), j > i and values[j] < values[i]
index i를 peak로 합칠 때 자기 자신이 두 배열에 모두 들어 있으므로 한 번 뺀다.
bitonic length at i = increasing[i] + decreasing[i] - 1
Java 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader reader = new BufferedReader(
new InputStreamReader(System.in)
);
int count = Integer.parseInt(reader.readLine());
int[] values = new int[count];
StringTokenizer tokenizer = new StringTokenizer(reader.readLine());
for (int index = 0; index < count; index++) {
values[index] = Integer.parseInt(tokenizer.nextToken());
}
int[] increasing = new int[count];
int[] decreasing = new int[count];
Arrays.fill(increasing, 1);
Arrays.fill(decreasing, 1);
for (int current = 0; current < count; current++) {
for (int previous = 0; previous < current; previous++) {
if (values[previous] < values[current]) {
increasing[current] = Math.max(
increasing[current],
increasing[previous] + 1
);
}
}
}
for (int current = count - 1; current >= 0; current--) {
for (int next = current + 1; next < count; next++) {
if (values[next] < values[current]) {
decreasing[current] = Math.max(
decreasing[current],
decreasing[next] + 1
);
}
}
}
int answer = 0;
for (int peak = 0; peak < count; peak++) {
answer = Math.max(
answer,
increasing[peak] + decreasing[peak] - 1
);
}
System.out.println(answer);
}
}
증가만 하거나 감소만 해도 바이토닉이다
문제 정의에서는 증가 부분이나 감소 부분의 길이가 1이어도 된다. 따라서 완전히 증가하는 수열은 마지막 값을 peak로, 완전히 감소하는 수열은 첫 값을 peak로 잡아 전체 길이가 답이 된다.
같은 값은 strictly increasing·decreasing 관계가 아니므로 비교에 <를 사용한다. 중복 값이 있어도 sequence index는 유지되며 같은 값끼리는 길이를 늘리지 않는다.
LIS 직후 10~20분 만에 푼 기록
원문에는 백준 11053 LIS를 약 3시간 만에 푼 직후 이 문제를 만나 10분 또는 20분 만에 응용했다고 적혀 있다. 처음 구현은 increasing 길이에만 자기 자신을 포함하고 decreasing은 0에서 시작해 두 값을 그대로 더했다.
그 방식도 일관되게 쓰면 맞지만, 두 배열을 모두 “현재 원소를 포함한 길이”로 정의하고 마지막에 1을 빼는 표준 형태가 상태를 설명하기 쉽다. 시간 복잡도는 두 번의 이중 loop로 O(N²), 공간은 O(N)이다.
검증 범위
Java source를 수동 검토하고 all-increasing, all-decreasing, duplicate, peak가 양끝인 경우와 작은 random sequence를 모든 subsequence brute force와 대조한다. 현재 환경에는 실제 JDK가 없어 compile·judge 재제출은 live 반영 전에 별도 확인이 필요하다.
'배움과 성장 > 알고리즘·문제풀이' 카테고리의 다른 글
| 백준 1912 연속합 Java: Kadane 알고리즘과 음수 배열 처리 (0) | 2022.04.03 |
|---|---|
| 백준 2565 전깃줄 Java: 정렬 후 LIS로 최소 제거 수 구하기 (0) | 2022.04.01 |
| 백준 11053 LIS Java: O(N²) 동적 계획법의 상태 정의 (0) | 2022.03.31 |
| 백준 2156 포도주 시식 Java: 마지막 잔을 고르지 않는 DP 점화식 (0) | 2022.03.29 |
| 백준 10844 쉬운 계단 수 Java: 자리수 DP와 모듈러 연산 (0) | 2022.03.28 |
댓글