본문 바로가기
(구) 자료/Baekjoon Online Judge

백준 #7453 / 합이 0인 네 정수

by 뜐뜐뜐 2018. 1. 26.
시간 제한메모리 제한제출정답맞은 사람정답 비율
2 초256 MB287846029717.753%

문제

정수로 이루어진 크기가 같은 배열 A, B, C, D가 있다.

A[a], B[b], C[c], D[d]의 합이 0인 (a, b, c, d) 쌍의 개수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 배열의 크기 n (1 ≤ n ≤ 4000)이 주어진다. 다음 n개 줄에는 A, B, C, D에 포함되는 정수가 공백으로 구분되어져서 주어진다. 배열에 들어있는 정수의 절대값은 최대 228이다.

출력

합이 0이 되는 쌍의 개수를 출력한다.



솔직히 이 문제는 잘 몰라서 다 뒤져봤다... 근데 Binary Search에서 Upper Bound와 Lower Bound에 대한 것을 하나 얻었다.

만족한다...ㅜㅠ


[How To Solve?]

- A, B, C ,D 를 모두 더해서 0인 값을 찾으면 좋겠지만, 그렇게하면 O(n^4)의 시간복잡도를 갖는다. 최악이다.

- 최대한 배열의 갯수를 줄여야한다. A와 B가 N개의 원소를 갖고 있다면 합쳐서 N^2개의 AB배열로 만든다.

- 만들어진 AB배열의 모든 원소를 기준으로 잡고, 정렬된 CD배열에서 Binary_Search로 절대 값은 갖고 부호가 다른 값을 찾는다.

- N*N번 반복해준다.

- 이때 Upper Bound, Lower Bound를 찾아서, 그 차이를 갯수에 축적시켜준다.

- 그 이유는, AB배열에서 -40이라는 값을 찾았다면, CD배열에서 40이라는 값을 찾았을 때, 갯수를 늘려주면 되는데

- 여기서 40이라는 값이 5개인 경우, BS를 5번이나 반복해야한다.

- 하지만 상한선(Upper Bound)과 하한선(Lower Bound)를 찾아준다면, 그 갯수의 차이를 축적시킴으로써

- 탐색으로 낭비되는 시간을 줄일 수 있다. 최적화가 가능하다는 의미.


[Code]

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
// boj.kr/7453
#include<iostream>
#include<algorithm>
#define MAX_SIZE 4001
using namespace std;
 
long long AB[MAX_SIZE * MAX_SIZE], CD[MAX_SIZE * MAX_SIZE];
 
int main() {
    cin.sync_with_stdio(false);
    cin.tie(NULL);
 
    long A[MAX_SIZE] = { 0, };
    long B[MAX_SIZE] = { 0, };
    long C[MAX_SIZE] = { 0, };
    long D[MAX_SIZE] = { 0, };
 
    int N;
    cin >> N;
 
    for (int i = 0; i < N; i++)
        cin >> A[i] >> B[i] >> C[i] >> D[i];
 
    int idx = 0;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            AB[idx] = A[i] + B[j];
            CD[idx++= C[i] + D[j];
        }
    }
    sort(CD, CD + N*N);    // BS로 찾을 기준 배열만 정렬.
    
    long long cnt = 0;
    long left = 0, right = N*N;
 
    for (int i = 0; i < N * N; i++) {    // AB배열의 처음부터 N*N번째까지 BS로 쌍을 찾는다.
 
        while (left < right) {
            long long mid = (left + right) / 2;
            if (AB[i] + CD[mid] < 0) left = mid + 1;
            else right = mid;
        }
        long lower_bound = right;
        left = 0, right = N*N;
 
        while (left < right) {
            long long mid = (left + right) / 2;
 
            if (AB[i] + CD[mid] <= 0) left = mid + 1;
            else right = mid;
        }
        long upper_bound = right;
 
        cnt += upper_bound - lower_bound;
        left = 0, right = N*N;
    }
    printf("%lld\n", cnt);
    return 0;
}
cs


// 주석

- 보통 BS에서 mid를 기준으로 값이 크면 left = mid+1, 작으면 right = mid-1로 옮겨간다.

- 하지만 여기서는 upper bound, lower bound를 찾는 것이 목표이므로 옮겨가지 않는다.

- 첫 번째 while문에서는 위에서 든 예처럼, 40보다 작지만 40보단 크지 않은 최대 정수를 찾으면, 그 값을 lower bound

- 두 번째 while문에서는 40 중에서 가장 큰 값을 찾아서 upper bound로 설정.

- 중복을 고려해야하는 문제는 BS로 Upper, Lower Bound 설정하는 스킬을 잘 이용할 줄 알아야겠다.