[백준] | C++ | 7453. 합이 0인 네 정수 (unordered_map, 해시 충돌)

2025. 4. 11. 10:30·알고리즘/C++

개요

solved.ac 클래스 5 문제 탐색 도중 제목이 제일 만만해 보여서 고른 문제입니다.

7453. 합이 0인 네 정수. 딱 봤을때 이분 탐색, 두 포인터 느낌이 나길래 골랐습니다. 용액 문제를 풀었을때 상당히 쉬웠거든요.

 

 

 


 

 

문제

정수로 이루어진 크기가 같은 배열 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이 되는 쌍의 개수를 출력한다.

 

테스트 케이스

입력 출력
6
-45 22 42 -16
-41 -27 56 30
-36 53 -37 77
-36 30 -75 -46
26 -38 -10 62
-32 -54 -6 45
5

 

 


 

 

풀이

알고리즘 분류는 예상했듯이 이분 탐색에 두 포인터입니다.

근데... 중간에서 만나기?

조금 낯선 키워드가 하나 껴있죠.

 

중간에서 만나기는 브루트포스 등의 방법을 사용해야 하는데 범위가 매우 커졌을때 사용하는 방법입니다. 탐색을 n등분 하고 그 n등분한 탐색 결과를 다시 탐색하는 그런 방법인데 분할 정복이랑 살짝 비슷한 면이 있어요.

 

들어오는 A, B, C, D 배열을 전부 브루트포스로 탐색하게 되면 시간 복잡도가 \(O(n^4)\)로 n이 최대 4000까지 들어오니 대강 256,000,000,000,000 정도라서 시간 제한(12초)을 한참 초과합니다.

그러니 배열을 나눠서 탐색을 해야겠죠.

 

아이디어 정리

제 아이디어는 다음 두 식에서 도출됩니다.

$$A[a] + B[b] + C[c] + D[d] = 0$$

$$A[a] + B[b] = -(C[c] + D[d])$$

 

\(A[a] + B[b]\)가 \(-(C[c] + D[d])\)와 같다면 4개를 합친 몫은 0이 되겠죠? 그러니 A와 B 배열을 탐색하면서 가능한 합연산의 몫을 모두 구합니다. 최대로 \(n^2\)개 정도 나올 수 있습니다.

 

그리고 다시 C와 D 배열을 탐색하면서 가능한 합연산의 몫을 모두 구합니다. 얘도 마찬가지로 최대 \(n^2\)개 까지 나와요.

 

그리고 뽑아낸 AB의 합과 와 CD의 합 배열을 정렬한 후 하나는 0부터 하나는 n*n 부터 탐색을 하면 됩니다.

이분 탐색에 두 포인터인데 그 포인터가 한 배열이 아니라 2개에 배열에 각각 떨어져 있는 느낌이죠.

일단 시간 복잡도를 대강 계산 했을 때 \(O(n^2 log n)\) 정도가 나옵니다.

 

 

...근데 이거 조금 맘에 안들어요. 굳이 합연산 배열을 2개 만들어서 또 다시 탐색할 필욘 없을 것 같습니다. 그래서 저는 map을 사용하기로 했어요.

 

일단 A와 B 배열을 탐색하면서 가능한 합연산의 몫을 모두 구합니다. 여기까진 똑같죠.

근데 이제 map을 사용해서 나온 몫에 개수를 캐싱합니다.

std::unordered_map<int, int> map
map[A + B]++;

Key의 합연산의 값을 넣고 Value엔 그 값이 나온 횟수를 넣는 것이죠.

 

그리고 이제 C와 D 배열을 탐색하면서 가능한 합연산의 몫을 모두 구해요. 근데 배열에 캐싱하는 대신 map에 Key로 집어 넣습니다.

근데 위에서 말했듯이 \(A[a] + B[b] = -(C[c] + D[d])\) 라면 \(A[a] + B[b] + C[c] + D[d] = 0\)죠.

그러니 다음과 같이 값을 찾습니다.

int cnt = 0;
cnt += map[-(C + D)]

 

이렇게 하면 시간 복잡도 \(O(n^2)\)만에 해결이 가능합니다.

 

 

코드로 풀기

아이디어가 다 나왔다면 코드로 푸는 것은 간단합니다.

일단 전 백준 문제 풀때 define을 쓰는 것을 즐기기 때문에 간단하게 define 몇개만 선언하고 갑시다.

#define A 0
#define B 1
#define C 2
#define D 3

 

 

그 뒤에 인풋 형식에 맞게 vector를 선언하고 값을 입력 받습니다.

int n;
cin >> n;

vector<vector<int>> numVec(D + 1, vector<int>(n));

for (int i = 0; i < n; i++)
{
	for (int j = A; j <= D; j++)
	{
		cin >> numVec[j][i];
	}
}

 

 

입력을 전부 받았으니 이제 A와 B 배열을 돌면서 가능한 합연산을 모두 구하고 그것을 unordered_map에 집어넣습니다.

std::unordered_map<int, int> sumAB;

for (int i = 0; i < n; i++) 
{
	for (int j = 0; j < n; j++) 
	{
		int sum = numVec[A][i] + numVec[B][j];
		sumAB[sum]++;
	}
}

똑같은 값이 또 나오면 그건 이제 map에 누적해서 들어갑니다.

 

 

그리고 C와 D 배열을 돌면서 가능한 합연산을 구합니다. 그리고 그 합에 -1을 곱하고 map에 Key로 집어넣으면 나오는 값을 count 변수에 모두 더해주면 됩니다.

size_t count = 0;
for (int i = 0; i < n; i++) 
{
	for (int j = 0; j < n; j++) 
	{
		int sum = numVec[C][i] + numVec[D][j];
		count += sumAB[-sum];
	}
}

 

count를 size_t로 둔 이유는 모든 입력이 0이 들어오면 \(n^4\)까지 count가 올라가기 때문입니다. n이 4000까지니까 \(4000^4\)이면 int로는 거들떠볼 수도 없는 값이 나옵니다.

 

 

근데 이렇게 하면...

메모리 초과가 발생합니다.

 

 

이유를 알아보니 C와 D를 탐색할때 unordered_map에 -sum이 Key인 값을 찾고 있는데 -sum인 Key가 존재하지 않으면 unordered_map에 -sum이 Key인 pair를 하나 더 만들기 때문에 메모리 초과가 발생할 수 밖에 없더군요. 그래서 로직을 수정해주겠습니다.

 

이러면 되겠죠.

size_t count = 0;
for (int i = 0; i < n; i++) 
{
	for (int j = 0; j < n; j++) 
	{
		int sum = numVec[C][i] + numVec[D][j];
		auto it = sumAB.find(-sum);
		if (it != sumAB.end()) 
		{
			count += it->second;
		}
	}
}

 

이제 시간 복잡도도 \(O(n^2)\)에 메모리도 낭비하지 않는 완벽한 풀이라고 생각했습니다.

??;;;

 

 

시간 초과는 예상치 못했습니다. unordered_map은 해시 테이블 기반이라 접근이 \(O(1)\)이라서 충분히 될 줄 알았는데요...

생각을 해보면 unordered_map에서 해시 충돌이 발생할 경우 Key 기반 접근의 시간 복잡도가 \(O(n)\)까지 증가할 수 있습니다. 그러니 unordered_map를 미리 reserve 해야했죠.

 

unordered_map의 reserve는 vector의 reserve와 비슷하면서도 다릅니다. 둘 다 미리 저장공간을 확보해놓는 것은 똑같지만 unordered_map은 해시 충돌이 나지 않도록 Key-Value Pair가 들어가는 Bucket이란 것을 미리 확보해둡니다. 이 Bucket이 적을 경우 Pair가 똑같은 Bucket에 들어갈 가능성이 증가하니 해시 충돌이 발생하는겁니다. 그래서 미리 Bucket을 많이 만들어두는 것이죠.

 

아무튼 이런식으로 reserve를 하면...

std::unordered_map<int, int> sumAB;
sumAB.reserve(n * n);

 

 

 

진짜 성공입니다.

 

 

 


 

 

 

마치는 말

접근 자체는 잘했는데 unordered_map의 특성을 여럿 놓쳐서 아쉬웠던 풀이입니다. 그래도 학습이 되었으니 OK 입니다.

 

 

코드 전문

#define FAST_PRINT std::ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL)

#define BEGIN_TO_END(vec) vec.begin(), vec.end()
#define A 0
#define B 1
#define C 2
#define D 3

int main()
{
	FAST_PRINT;

	int n;
	cin >> n;

	vector<vector<int>> numVec(D + 1, vector<int>(n));

	for (int i = 0; i < n; i++)
	{
		for (int j = A; j <= D; j++)
		{
			cin >> numVec[j][i];
		}
	}

	std::unordered_map<int, int> sumAB;
	sumAB.reserve(n * n);

	for (int i = 0; i < n; i++) 
	{
		for (int j = 0; j < n; j++) 
		{
			int sum = numVec[A][i] + numVec[B][j];
			sumAB[sum]++;
		}
	}

	size_t count = 0;
	for (int i = 0; i < n; i++) 
	{
		for (int j = 0; j < n; j++) 
		{
			int sum = numVec[C][i] + numVec[D][j];
			auto it = sumAB.find(-sum);
			if (it != sumAB.end()) 
			{
				count += it->second;
			}
		}
	}

	cout << count;
}

'알고리즘 > C++' 카테고리의 다른 글

[백준] | C++ | 10942. 펠린드롬? (Top-down DP)  (2) 2025.04.20
[백준] | C++ | 4991. 로봇 청소기 (TSP, 비트마스킹)  (1) 2025.04.19
[백준] | C++ | 17626. Four Squares (Bottom-up DP, 브루트포스)  (0) 2025.04.05
[백준] | C++ | 2239. 스도쿠 (사진 설명 포함)  (0) 2024.12.27
[백준] | 골드5 달성!  (3) 2024.10.28
'알고리즘/C++' 카테고리의 다른 글
  • [백준] | C++ | 10942. 펠린드롬? (Top-down DP)
  • [백준] | C++ | 4991. 로봇 청소기 (TSP, 비트마스킹)
  • [백준] | C++ | 17626. Four Squares (Bottom-up DP, 브루트포스)
  • [백준] | C++ | 2239. 스도쿠 (사진 설명 포함)
SundG0162
SundG0162
블로그 프로필은 머핀입니다.
  • SundG0162
    게임개발고수가될거야
    SundG0162
  • 전체
    오늘
    어제
    • 분류 전체보기 (79) N
      • 주저리 (3)
        • 잡담 (2)
        • 장현우 (0)
        • 회고록 (1)
      • 개발 (51) N
        • C# (5) N
        • CITADEL : 성채 (1)
        • HEXABEAT (1)
        • FRACTiLE (6)
        • UNNAMED (9)
        • Default Defense (10)
        • T-Engine (1)
        • Project EW (0)
        • 졸업작품 (0)
        • Unity (3)
        • C++ (13)
        • WinAPI (1)
        • 그 외 (0)
      • 알고리즘 (13)
        • C# (1)
        • C++ (12)
      • 자료구조 (2)
        • C++ (1)
        • C# (0)
        • 공용 (1)
      • 기타 (10)
        • 아트 (6)
        • AI (2)
        • 수학 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    C#
    코딩테스트
    코딩
    AI
    생성형ai
    LLM
    코드트리
    티스토리챌린지
    코딩트리조별과제
    오블완
    유니티
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
SundG0162
[백준] | C++ | 7453. 합이 0인 네 정수 (unordered_map, 해시 충돌)
상단으로

티스토리툴바