알고리즘 문제

[C/C++ 백준 1912번] 연속합 (Silver 2)

새파란 공대생 2020. 5. 5. 21:54

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

 

1912번: 연속합

첫째 줄에 정수 n(1 ≤ n ≤ 100,000)이 주어지고 둘째 줄에는 n개의 정수로 이루어진 수열이 주어진다. 수는 -1,000보다 크거나 같고, 1,000보다 작거나 같은 정수이다.

www.acmicpc.net

처음 이문제를 보자마자 생각한건 배열을 두번 돌면서 모든 i j 에 대해 i부터 j까지의 합을 구해 Max를 구하는 거였지만,, 딱봐도 시간 초과나 뭐 그런 오류가 뜰게 분명하다.

 

분류가 다이나믹 프로그래밍이니, 쪼개서 생각해보자. 일단 떠오르는 아이디어는 N-1에서 구한 연속합을 이용해 N번째의 연속합을 구하는 것이다.

 

N번째 수가 음수일경우 연속합은 N-1번째와 같다.

N번째 수가 양수일경우 ① N번째 수 ② N-1번째 연속합 ③ N-1번째 연속합에서 N번째 수까지의 모든 합

이 세가지 중 가장 큰 것을 선택하면 될 것 같다. 이제 코딩해보자. 


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
#include <stdio.h>
int FindBest(const int num[], int j, int& BestCombiBegin, const int BestCombi[]){
    if(j==0){
        BestCombiBegin=0;
        return num[0];
        }
    if(j<0)
        return BestCombi[j-1];
    else{
        int sum=0;
        for(int i=BestCombiBegin; i<=j; i++)
            sum += num[i];
        if(sum>=BestCombi[j-1&& sum >= num[j])
            return sum;
        else if(BestCombi[j-1>= sum && BestCombi[j-1>= num[j])
            return BestCombi[j-1];
        else{
            BestCombiBegin = j;
            return num[j];
        }
    }
        
}
int main(void){
    int n, BestCombiBegin=-1,N[100001]={};
    int BestCombi[100001]={};
    scanf("%d",&n);
    for(int i=0; i<n; i++){
        scanf(" %d",&N[i]);
    }
    for(int j=0; j<n; j++){
        BestCombi[j]=FindBest(N, j, BestCombiBegin, BestCombi);
    }
    printf("%d",BestCombi[n-1]);
    printf("\n%d",BestCombiBegin);
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

틀렸다. 예제 입력 1에서 10 -4 3 1 5 6 -35 12 21 -1 경우 12 21 조합을 인지 할 수 있어야 하는데, 알고리즘상 그게 불가능하다. 한 시간 정도 고민하다가 글 쓰는 공돌이님의 풀이(https://sihyungyou.github.io/baekjoon-1912/)를 보고 30분 정도 더 고민한 끝에 왜 저렇게 하면 풀리는지 이해할 수 있었다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <cstdio>
#include <algorithm>
using namespace std;
int main(void){
    int n, num[100001]={};
    int dp[100001]={};//dp[i]는 i를 반드시 포함하는 연속합을 의미한다. 
    int ans;
    scanf("%d",&n);
    for(int i=0; i<n; i++)    scanf(" %d",&num[i]);
    ans = num[0];
    dp[0= num[0];
    for(int j=1; j<n; j++){
        dp[j]=max(dp[j-1]+num[j], num[j]);
        ans=max(dp[j], ans);
    }
    printf("%d",ans);
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 
 

dp[i]를 구하는 과정에서, 새로운 연속합을 만들지 아니면 기존의 것에서 더해갈지 결정하게 된다. 새로운 연속합이 기존의 연속합보다 크든 작든 ans 구하는 과정에서 고려하니 신경 쓸 필요도 없고.

위의 예제 입력 1에서 10 -4 3 1 5 6 -35 12 21 -1 에서도 12를 거칠 때 연속합이 12를 포함하게 되면서 12 21을 고려할 수 있게 된다.

 

과정이 별로 없어 보이는데, 빈틈없이 코드가 굴러가는 게 정말 신기하긴 하다. 열심히 해야겠다.