※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 7453번 문제인 합이 0인 네 정수이다.
문제는 아래 링크를 확인하자.

www.acmicpc.net/problem/7453

 

7453번: 합이 0인 네 정수

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

www.acmicpc.net

이 문제는 주어진 네 배열 A, B, C, D에서 index를 하나씩 골라 그 위치의 정수들의 합이 0이 되는 경우의 수를 구하는 문제이다.

주어진 예제의 배열 A에서 보이듯, 이 문제는 각 배열에 중복원소가 등장할 수 있다.

index를 고르는 경우의 수를 구하는 것이기에 위 경우의 중복원소는 다 따로 계수해야 한다.

 

이 문제를 풀기 위해 다음과 같은 코드를 구상한다.

1) A와 B의 각 원소의 합으로 구성된, 길이 n^2 배열 AB를 만든다. [O(n^2)]

2) C와 D의 각 원소의 합에 -1을 곱한 길이 n^2 배열 CD를 만든다. [O(n^2)]

배열 AB와 CD를 이용하면 이 문제를 배열 AB의 원소와 CD의 원소에서 같은 원소를 찾는 문제로 생각할 수 있다.

3) AB와 CD를 크기순으로 정렬한다. [O(n^2 logn)]

4) 투 포인터로 문제를 해결한다.

AB의 0번 원소를 가리키는 ptab와 CD의 0번 원소를 가리키는 ptcd를 만든다.

둘이 가리키는 원소가 같다면, AB에 있는 그 원소의 개수와 CD에 있는 그 원소의 개수를 포인터를 옮기며 센다.

그 곱을 ans변수에 더해준다.

ptab가 가리키는 원소가 작다면 ptab를 한칸 옮기고, ptcd가 가리키는 원소가 작다면 ptcd를 한칸 옮긴다.

두 포인터중 하나가 배열을 벗어날 때까지 위 내용을 반복한다.

 

문제의 제약조건에서 네 수의 합이 int 범위를 넘어갈 수 없다는 것을 알 수 있으나, ans의 값은 int 범위를 넘어갈 수 있음에 유의한다. 글쓴이는 이 점을 놓쳐 오답을 여러 번 제출했다.

 

아래는 제출한 소스코드이다.

#include <iostream>
#include <algorithm>
using std::cin;
using std::cout;
using std::sort;

int A[4000];
int B[4000];
int C[4000];
int D[4000];
int AB[16000000];
int CD[16000000];

int main()
{
    std::ios::sync_with_stdio(0);
    cin.tie(0);
    
    int n; // 배열 A,B,C,D를 읽어오기
    cin >> n;
    int a, b, c, d;
    for (int i = 0;i < n;i++) {
        cin >> a >> b >> c >> d;
        A[i] = a;
        B[i] = b;
        C[i] = c;
        D[i] = d;
    }
    for (int i = 0;i < n;i++) { // 배열 AB 계산
        for (int j = 0;j < n;j++) {
            AB[n * i + j] = A[i] + B[j];
        }
    }
    for (int i = 0;i < n;i++) { // 배열 CD 계산
        for (int j = 0;j < n;j++) {
            CD[n * i + j] = -(C[i] + D[j]);
        }
    }
    sort(AB, AB + (n * n)); // AB와 CD 정렬
    sort(CD, CD + (n * n));
    long long ans = 0; // ans, 투포인터로 사용할 변수 선언
    int abpt = 0;
    int cdpt = 0;
    while (abpt < n * n and cdpt < n * n) { // 두 포인터 모두 배열 범위에 들어있을 동안 반복
        if (AB[abpt] == CD[cdpt]) { // 같은 경우
            int x = AB[abpt];
            int c1 = 0, c2 = 0;
            while (AB[abpt] == x) { // AB에서 포인터를 옮기면서 같은 원소의 개수 c1을 센다
                abpt++;
                c1++;
                if (abpt == n * n) break; // 포인터가 배열을 넘어가는 경우 예외처리
            }
            while (CD[cdpt] == x) { // CD에서 포인터를 옮기면서 같은 원소의 개수 c2를 센다
                cdpt++;
                c2++;
                if (cdpt == n * n) break; // 포인터가 배열을 넘어가는 경우 예외처리
            }
            ans += (long long) c1 * (long long) c2; // 
        }
        else if (AB[abpt] < CD[cdpt]) abpt++; // 다른 경우 작은 수를 가리키는 쪽 포인터를 옮긴다
        else cdpt++;
    }
    cout << ans;
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 6615 // C++] 콜라츠 추측  (0) 2021.01.08
[BOJ 5393 // C++] 콜라츠  (0) 2021.01.07
[BOJ 10815 // C++] 숫자 카드  (0) 2021.01.05
[BOJ 1026 // C++] 보물  (0) 2021.01.04
[BOJ 1977 // C++] 완전제곱수  (0) 2021.01.03

+ Recent posts