View

300x250

문제링크

https://www.acmicpc.net/problem/11054

문제

수열 S가 어떤 수 Sk를 기준으로 S1 < S2 < ... Sk-1 < Sk > Sk+1 > ... SN-1 > SN을 만족한다면, 그 수열을 바이토닉 수열이라고 한다.

예를 들어, {10, 20, 30, 25, 20}과 {10, 20, 30, 40}, {50, 40, 25, 10} 은 바이토닉 수열이지만,  {1, 2, 3, 2, 1, 2, 3, 2, 1}과 {10, 20, 30, 40, 20, 30} 은 바이토닉 수열이 아니다.

수열 A가 주어졌을 때, 그 수열의 부분 수열 중 바이토닉 수열이면서 가장 긴 수열의 길이를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 수열 A의 크기 N이 주어지고, 둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ N ≤ 1,000, 1 ≤ Ai ≤ 1,000)

출력

첫째 줄에 수열 A의 부분 수열 중에서 가장 긴 바이토닉 수열의 길이를 출력한다.

조건

  • 시간 제한 : 1s
  • 메모리 제한 : 256MB

해설

수 하나를 기준으로 좌우로 수를 뽑았을 때, index 순서대로 오름차순 / 내림차순을 만들 수 있는 뽑은 수열의 길이의 최댓값을 묻는 문제이다. 예를 들어, 다음과 같이 바이토닉 수열을 만들 수 있다.

  • 1 5 2 1 4 3 4 5 2 1 → 바이토닉 수열의 최대 길이 : 7
  • 1 5 2 1 4 3 4 5 → 바이토닉 수열의 최대 길이 : 5 ← 오름치순 또는 내림차순이 없을 수도 있음!
  • 1 2 3 2 1 7 → 바이토닉 수열의 최대 길이 : 5 ← 최대 숫자가 기준이 아님!

주어진 수열에서 왼쪽에서 오른쪽으로 갈 때 오름차순으로 만들 수 있는 조건을 따져 처음부터 해당 index까지에서 만들 수 있는 최대 오름차순 수열의 길이를 구하고, 왼쪽에서 오른쪽으로 갈 때 오름차순으로 만들 수 있는 조건을 따져 끝부터 해당 index까지에서 만들 수 있는 최대 오름차순 수열의 길이를 구하여 합치면 쉽게 해결할 수 있다.

풀이

아래 수열을 예시로 하여 해설에서의 해결 방법을 적용해보자.

input : 1 5 2 1 4 3 4 5 2 1

incre : 1 2 2 1 3 3 4 5 2 1
decre : 1 5 2 1 4 3 3 3 2 1
lengt : 1 6 3 1 6 5 6 7 3 1

좌→우 방향으로 오름차순인 increase와 좌←우 방향으로 오름차순(좌→우 내림차순)인 decrease의 합에서 1을 뺀 length는 해당 index를 좌우를 나누는 기준으로 선택했을 때 만들 수 있는 바이토닉 수열의 길이이다. 1을 빼는 이유는 자신이 2번 수열에 포함되기 때문!

각 i번째 원소 / N-1-i 번째 원소가 좌→우 / 우→좌 방향으로 오름차순 수열에 포함되면 최대 몇 번째 원소가 될 수 있는지를 구하여 increase / decrease에 저장한다. 뒤에서 앞으로 오는 decrease는 역순으로 저장해야 좌→우 방향으로 읽었을 때 내림차순으로 만들 수 있으므로 index가 뒤에서부터 온다. 이를 코드로 구현하면 아래와 같다.

for(int i = 0; i < N; i++) {
    for(int j = 0; j < i; j++) {
        if(input[j] < input[i]) {
            if(increase[i] < increase[j]+1) {
                increase[i] = increase[j] + 1;
            }
        }
        if(input[N-1-j] < input[N-1-i]) {
            if(decrease[N-1-i] < decrease[N-1-j] + 1) { 
                decrease[N-1-i] = decrease[N-1-j] + 1;
            }
        }
    }
}

increase와 decrease의 같은 index간의 합 중 가장 큰 값에서 1을 뺀 값이 원하는 가장 긴 바이토닉 수열의 길이이다. 이 값을 출력하면 원하는 결과를 얻을 수 있다.

int max = 0;
for(int i = 0; i < N; i++) {
    increase[i] = increase[i]+decrease[i];
    max = (max > increase[i])? max : increase[i];
}

cout << max - 1;

코멘트

https://sihyungyou.github.io/baekjoon-11054/ 여기에서 도움을 얻었다. 어렵군... 어려워...

고민

처음에는 가장 큰 값을 잡고 좌우로 오름/내림차순을 돌리려고 했는데, 이 방법은 해결책이 아니더라. 위에 예시를 든 것 처럼 가장 큰 값이 선택의 기준이 되면 최댓값을 못갖는 경우가 생겼었다.

그 다음에는 좌/우로 LDS 알고리즘을 따로 적용해서 각 길이를 구하려고 시도했었다. 이 방법의 문제는, 어떤 원소를 좌우로 나누는 기준점으로 골라야 LDS를 적용했을 때 정확한 원소 길이를 구할 수 있는지를 알 수 없다는 것이였다.

그래서 검색에서 위 블로그의 도움을 받아 문제를 해결할 수 있었다. 고통스러웠다. ㅋㅋㅋ. 자꾸 검색을 너무 쉽게 하는 것 같은 느낌이 들기도 하는데, LDS 알고리즘의 시간 복잡도가 오래 걸리는 버전을 사용해야 한다는 것을 놓쳤던 것 같다. 나는 O(n log n) 방법밖에 모른단 말야... ㅋㅋㅋㅋ


코드

#include <iostream>
#include <vector>

using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    
    int N;
    cin >> N;

    // 입력    
    vector<int> input(N);
    int temp;
    for(int i = 0; i < N; i++) {
        cin >> temp;
        input[i] = temp;
    }
    
    // DP
    vector<int> increase(N, 1);
    vector<int> decrease(N, 1);
    
    for(int i = 0; i < N; i++) {
        for(int j = 0; j < i; j++) {
            if(input[j] < input[i]) {
                if(increase[i] < increase[j]+1) {
                    increase[i] = increase[j] + 1;
                }
            }
            if(input[N-1-j] < input[N-1-i]) {
                if(decrease[N-1-i] < decrease[N-1-j] + 1) { 
                    decrease[N-1-i] = decrease[N-1-j] + 1;
                }
            }
        }
    }
    
    int max = 0;
    for(int i = 0; i < N; i++) {
        increase[i] = increase[i]+decrease[i];
        max = (max > increase[i])? max : increase[i];
    }
    
    cout << max - 1;
    return 0;
}
320x100

'Develop > 알고리즘' 카테고리의 다른 글

[백준] 1904 - 01타일  (0) 2021.12.25
[백준] 11048 - 이동하기  (0) 2021.12.25
[백준] 17298 - 오큰수  (0) 2021.12.25
[백준] 2293 - 동전 1  (0) 2021.12.25
[백준] 2304 - 창고 다각형  (0) 2021.12.25
Share Link
reply
반응형
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31