[ BOJ Code ]/# BOJ -

[ 백준 13398 ] 연속합2 (C++)

얍문 2021. 1. 18. 14:29

백준의 연속합2(13393) 문제이다.

[ 문제 바로가기 ]


[ 문제풀이 ]

연속된 수 몇 개를 선택해서 구할 수 있는 합 중 가장 큰 합을 구해야 하는 문제이다.

그 과정에서, 수는 단 한 개 이상 반드시 선택해야 하며, 수열에서 수 하나를 제거할 수 있다.

그럼 우리는 다음과 같이 2가지 나눠서 연속합을 구해볼 수 있다.

1. 숫자를 제거하지 않고 연속합의 최대합을 구하는 경우

2. 숫자 하나를 제거하고 연속합의 최대합을 구하는 경우

각각의 방법들에 대해서 구체적으로 이야기해 보자.


#1. 숫자를 제거하지 않고 연속합의 최대합을 구하는 경우

숫자를 제거하지 않고 연속합을 구할 때, 그 최대합을 구하기 위해서는 어떤 경우를 비교해봐야 하는지 알아보자.

다음과 같은 수열이 있다고 가정해보자. [ 1 , 2 , -10 , 4 , 5 ]

그리고 왼쪽에서부터 하나씩 값을 선택해가면서, 연속합의 최대합을 구해보자.

* 첫 번째 원소 '1'

가장 첫번째 '1'은 비교를 할 수 없기 때문에 무조건적으로 선택되어 질 것이다.

[ 현재까지의 연속합의 최대합 = 1 ]

* 두 번째 원소 '2'

두 번째 '2'를 선택해보자. 두 번째값인 '2'를 선택할 때, 2가지 선택권이 우리에게 주어진다.

1. 기존의 연속합 + '2'

2. 기존의 연속합을 버리고 '2'부터 새로운 연속합의 시작

1번 같은 경우에는 기존의 연속합은 '1'이므로 1 + 2 = '3' 이라는 연속합을 구할 수 있고

2번 같은 경우에는 기존의 연속합을 버리고 '2부터 새로운 연속합을 시작하게 되므로 '2' 라는 연속합을 구할 수 있다.

그러므로 이 중 더 큰 연속합인 1번 경우를 선택해서 '3'이라는 연속합을 구할 수 있다.

[ 현재까지의 연속합의 최대합 = 3 ]

* 세 번째 원소 '-10'

두 번째 원소와 마찬가지로 2가지 선택권 중 더 큰 값을 선택하면 된다.

기존의 연속합을 포함해서 연속합을 계산하면 기존의 연속합 '3' + (-10) = -7 이 되고,

기존의 연속합을 버리고 '-10'부터 새로운 연속합을 시작하면 -10 이 되므로 더 큰 연속합은 -7이 된다.

[ 현재까지의 연속합의 최대합 = -7 ]

우리는 아직 "숫자를 제거하지 않는 경우" 에 대해서 계산을 하고 있으므로 이 과정에서 -10을 없애버릴 수는 없다.

* 네 번째 원소 '4'

마찬가지로 계산해보자.

-7 + 4 = -3 or '4'

위와 같이 -3 혹은 4 두 개의 연속합이 나오게 되는데, 이 떄 더 큰 값은 '4'가 된다.

[ 현재까지의 연속합의 최대합 = 4 ]

* 마지막 원소 '5'

마찬가지로 계산해보면, '9'라는 연속합을 구할 수 있다.

즉 ! 결과적으로 본다면 "숫자를 제거하지 않는 경우" 우리는 연속합의 최대값은 '9'라는 것을 알 수 있다.

이런식으로 우리는 "숫자를 제거하지 않고 연속합을 구했을 때 연속합의 최댓값" 을 구할 수가 있다.

이를 코드로 표현해보자.

1
2
3
4
5
6
7
Left_Sum[1= Arr[1];
Answer = Left_Sum[1];
for(int i = 2; i <= N; i++)    
    Left_Sum[i] = Max(Arr[i], Left_Sum[i - 1+ Arr[i]);
    Answer = Max(Answer, Left_Sum[i]);
}
cs

코드로 표현하면 위와 같이 표현할 수 있다.

위에서 사용된 변수들 부터 간단하게 이야기해보면, Left_Sum[] 이라는 배열은 다음과 같은 의미를 가진다.

Left_Sum[A] = B : A번 원소까지 연속합의 최댓값은 B입니다.

즉, 우리가 위에서 했듯이, A번 원소까지의 연속합의 최댓값을 구하기 위해서는 A - 1번째 원소까지의 연속합의 최댓값 + A번째 원소를 했을때의 값 혹은 이전까지의 연속합을 버리고 A원소부터 새로운 연속합을 시작하는 경우 중 더 큰 값을 선택해주면 된다.

따라서 line5)와 같이 비교를 해주고 있다. i번째 원소부터 새로운 연속합을 시작하는 경우와 i - 1번째 원소까지의 연속합의 최댓값에 i번째 원소의 값을 더해주는 경우 이 2가지를 비교해서 더 큰 값을 저장해 주고 있음을 확인할 수 있다.

그리고 line6)에서 'Answer'라는 변수에 최종적으로 연속합의 최댓값을 저장해주고 있다.


#2. 숫자를 제거하는 경우

그렇다면, 지금부터는 숫자를 제거하는 경우에 대해서 알아보자.

먼저, 숫자를 제거했을 때, 연속합을 구하기 위해서는 어떤 값들을 미리 알고 있어야 하는지 간단하게 그림을 통해서 알아보자.

.

배열이 위와 같이 주어졌다고 가정해보자.

위의 경우에서의 정답은 '21'이다. 가장 가운데 있는 '-500' 원소를 제거하고 연속합을 구하게 되면, 1 + 2 + 3 + ... + 6 으로

21이라는 답을 구할 수 있다.

그렇다면, -500을 제거해야 한다면 어떤 부분에 대한 연속합을 알고 있어야 할까 ?

.

바로 위의 그림과 같이 파랑색 동그라미로 표시되어 있는 부분들(A , B)에 대한 연속합을 알고 있어야 한다.

그런데 우리는 이미 #1에서 숫자를 제거하지 않았을 때의 연속합에 대해서 구해놓았다.

Left_Sum[] 이라는 배열을 이용해서 이 값을 저장해 놓았다.

즉 ! A부분을 Left_Sum[] 으로 표현해본다면, Left_Sum[3] 이 될 것이다.

왜냐하면, Left_Sum[3] 이 가지는 의미는 "3번째 원소까지 연속합의 최댓값" 이기 때문이다.

그렇다면, B부분은 어떻게 표현할 수 있을까 ??

Left_Sum[7] 로 표현이 가능할까 ?? 그렇지 않다. 물론, 위의 경우는 너무 극단적이다 보니 Left_Sum[7]로 계산을 하더라도 정답이 나올 것이다. 하지만 그렇지 않을 경우도 있을 것이다.

.

배열이 이런식으로 주어졌다고 가정을 해보자. 물론, 위의 경우는 모두 양수이기 때문에 숫자를 제거하지 않고 연속합을 구했을 때의 연속합이 최댓값이고 그 값이 정답이 될 것이다. 하지만, 만약 위의 경우에서 가장 가운데 있는 '4'를 제거해본다고 가정해보자. 과연 Left_Sum[3] + Left_Sum[7] 로 계산을 한다면, A부분과 B부분에 대한 합을 구할 수 있을까 ??

아마 제대로 구해지지 않을 것이다.

보다 구체적으로 이야기 해보자면, A부분을 표시하고 있는 Left_Sum[3] 에는 문제가 없지만, B부분을 표시하고 있는 Left_Sum[7] 은 올바른 표현이 아닐 것이다. 왜냐하면, Left_Sum[7]에는 1 + 2 + 3 + 4 + 4 + 5 + 6 의 값이 저장되어 있을 것이기 때문이다. 따라서 'B'부분을 정확하게 표현하고 있는 것이 아니다.

그래서 우리는 이 B부분을 위해서 역순으로 연속합을 구했을 때의 최댓값 또한 알고 있어야 한다.

이 역순으로 계산한 값을 Right_Sum[] 이라고 표현해보자.

즉, Right_Sum[7]에는 '6', Right_Sum[6] 에는 '6 + 5 = 11' , Right_Sum[5] = '6 + 5 + 4 = 15'

이런식으로 값이 저장될 것이다.

따라서 #1의 과정과 똑같은 방식으로 역순으로 계산했을 때 연속합의 최댓값을 구해줘야 한다.

그리고 이를 바탕으로 이제 주어진 배열에서 K번째 원소를 제거하고 난 후의 연속합을 구해보자.

K번째 원소를 제거한다면, K번째 원소를 기준으로 A부분, B부분으로 나뉘게 될 것인데,

A부분은 Left_Sum[K - 1], B부분은 Right_Sum[K + 1]로 표현이 가능할 것이다.

이런식으로 모든 원소를 하나씩 제거해보면서 모든 경우를 비교 및 정답을 갱신해 주면 된다.


[ 소스코드 ]

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
 
#define endl "\n"
#define MAX 100010
using namespace std;
 
int N, Answer;
int Arr[MAX];
int Left_Sum[MAX];
int Right_Sum[MAX];
 
int Max(int A, int B) { return A > B ? A : B; }
    
void Input()
{
    cin >> N;
    for (int i = 1; i <= N; i++cin >> Arr[i];
}
 
void Solution()
{
    if (N == 1)
    {
        cout << Arr[1<< endl;
        return;
    }
 
    Left_Sum[1= Arr[1];
    Answer = Left_Sum[1];
    for(int i = 2; i <= N; i++)    
    { 
        Left_Sum[i] = Max(Arr[i], Left_Sum[i - 1+ Arr[i]);
        Answer = Max(Answer, Left_Sum[i]);
    }
    Right_Sum[N] = Arr[N];
    for (int i = N - 1; i >= 1; i--)
    {
        Right_Sum[i] = Max(Arr[i], Right_Sum[i + 1+ Arr[i]);
    }
    
    for (int i = 2; i <= N - 1; i++) Answer = Max(Answer, Left_Sum[i - 1+ Right_Sum[i + 1]);
    
    cout << Answer << endl;
}
 
void Solve()
{
    Input();
    Solution();
}
 
int main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
 
    //freopen("Input.txt", "r", stdin);
    Solve();
 
    return 0;
}
cs