본문 바로가기

문제 노트/백준

세 수의 합( BOJ 2295 )

문제 : https://www.acmicpc.net/problem/2295

 

2295번: 세 수의 합

우리가 x번째 수, y번째 수, z번째 수를 더해서 k번째 수를 만들었다라고 하자. 위의 예제에서 2+3+5=10의 경우는 x, y, z, k가 차례로 1, 2, 3, 4가 되며, 최적해의 경우는 2, 3, 4, 5가 된다. k번째 수가 최

www.acmicpc.net

 

문제 파악하기

N개의 숫자로 이루어진 집합 U에서 3개의 숫자를 적절하게 선택해서 집합 U에 있는 값 중 만들 수 있는 가장 큰 값을 만드는 문제입니다. N의 개수가 최대 1,000개이기 때문에 O(n3)알고리즘으로는 시간초과가 나오게 됩니다. 그렇다면 어떤 알고리즘을 사용해서 시간 복잡도를 줄일 수 있을까요? 여기서는 3개의 숫자의 합을 구하는 수식을 나눌 필요가 있습니다.

 

문제 해결하기

우선, 3개의 숫자(a, b, c)를 사용해서 집합 U의 값(k)를 아래와 같이 만들었다고 가정해보겠습니다.

a + b + c = k --- (1)

 

위의 식을 그대로 알고리즘에 도입하면 우리는 3중 반복문으로 구현하게 됩니다. 하지만 N의 값이 크기 때문에 3중 반복문을 사용한 알고리즘으로는 1초라는 시간은 턱없이 부족합니다. 따라서, 우리는 위 식을 다음과 같이 바꿔보겠습니다.

a + b = k - c --- (2)

 

이렇게 식을 바꾸면 우리는 2중 반복문 2개를 사용하는 알고리즘을 만들 수 있습니다. 첫 번째 반복문은 두 숫자의 합을 구하고, 두 번째 반복문은 두 수의 차를 구한 다음에, 지금 구한 값(두 수의 차)이 이전 반복문에서 만들어진 값인지 확인해보면 됩니다. N이 최대 1,000개이기 때문에 2중 반복문은 충분히 제한시간 안에 작동하며, 2중 반복문 2개 역시 제한시간 안에 작동합니다. 이렇게 반복문의 횟수를 줄이면 문제를 효율적으로 해결할 수 있습니다. 자세한 소스코드는 다음과 같습니다.

 

소스코드

우선, 첫 번째 소스코드는 해시테이블을 사용해 모든 합을 확인하는 알고리즘입니다.

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
#include <stdio.h>
#include <map>
#include <vector>
#define NMAX 1010
using namespace std;
 
int N, t;
vector< int > inp, psum;
 
int ret;
map< intint > check;
 
int main() {
    scanf("%d"&N);
    for(int i=1;i<=N;i++) {
        scanf("%d"&t);
        inp.push_back(t);
    }
 
    for(int i=0;i<N;i++) {
        for(int j=i;j<N;j++) {
            psum.push_back(inp[i]+inp[j]);
 
            check[inp[j]-inp[i]] = inp[j];
        }
    }
 
    for(int val:psum) ret = max(ret, check[val]);
 
    printf("%d", ret);
}
cs

 

두 번째 소스코드는 위의 (2)번 식을 직관적으로 구현한 알고리즘입니다.

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
#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;
 
int N, t;
vector< int > inp, psum;
 
int ret;
 
bool checkValue(int val) {
    int l,r,mid;
 
    l=0; r=psum.size()-1;
    while(l<=r) {
        mid = (l+r)/2;
 
        if(psum[mid] == val) return 1;
        else if(psum[mid] < val) l = mid+1;
        else r = mid-1;
    }
 
    return 0;
}
 
int main() {
    scanf("%d"&N);
    for(int i=1;i<=N;i++) {
        scanf("%d"&t);
        inp.push_back(t);
    }
    sort(inp.begin(), inp.end());
 
    for(int i=0;i<N;i++) {
        for(int j=i;j<N;j++) {
            psum.push_back(inp[i]+inp[j]);
        }
    }
    sort(psum.begin(), psum.end());
 
    for(int i=N-1;i>=0 and !ret;i--) {
        for(int j=i;j>=0 and !ret;j--) {
            if(checkValue(inp[i]-inp[j])) {
                ret = inp[i];
            }
        }
    }
 
    printf("%d", ret);
}
cs

후기

Meet in Middle 알고리즘을 연습하기에 적절한 문제라고 생각합니다. 저 역시 이 문제를 통해 이 알고리즘의 작동방식을 이해하게 되었습니다. 이 알고리즘을 좀 더 연습하고 싶다면 이 문제(https://www.acmicpc.net/problem/1450)가 적절하다고 생각합니다. 참고로 알고리즘의 개념을 배울 때에는 이 영상(https://www.youtube.com/watch?v=57SUNQL4JFA)이 도움이 되었습니다.

'문제 노트 > 백준' 카테고리의 다른 글

수 지우기( BOJ 1467 )  (0) 2022.05.14
아우으 우아으이야!!( BOJ 15922 )  (0) 2022.04.29
1의 개수 세기( BOJ 9527 )  (0) 2022.04.12
레이스( BOJ 1508 )  (0) 2022.04.01
Cereal( BOJ 18878 )  (0) 2022.03.07