프로그래밍/이분 탐색

[BOJ 10816] 숫자 카드2

kkalgo 2022. 9. 15. 18:03

 

 

10816번: 숫자 카드 2

첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,

www.acmicpc.net

C++ STL이 새삼 정말 간편하다고 느낀 문제입니다..

이분 탐색을 위해 '정렬'을 한 후 중복까지 해결할 방법을 고민하는 과정에서 시간이 꽤 많이 걸렸습니다.

 

일단 카드의 개수가 500,000개이기 때문에 O(N^2) 이용하면 100% 시간초과입니다.

카드를 '찾는' 것이기 때문에 이분탐색을 바로 떠올릴 수 있지만, 그 카드가 '몇 장 있는지'까지 찾아야 하기 때문에

단순히 찾는 것 이상으로 더 고민해봐야 합니다.

10
6 3 2 10 10 10 -10 -10 7 3
8
10 9 -5 2 3 4 5 -10

이분 탐색을 사용하기 위해

  1) 처음 입력받는 카드를 정렬을 합니다.

     6 3 2 10 10 10 -10 -10 7 3

→ -10 -10 2 3 3 6 7 10 10 10

 

우리가 궁금한 것은 찾은 카드가 '몇 장'인지입니다. 근데, 어떤 카드가 '몇 장'인지를 미리 기록 해둔다면?

그러면 카드를 찾았을 때 그 카드가 몇 장인지를 기록해두었기 때문에 바로 return해주면 되겠습니다.

 

위에 정렬되있는 숫자들이 중복을 제외하면 -10,2,3,6,7,10 입니다.

이들을 pair<값, 몇 장>의 개념으로 저장하고 pair.first(값) == 찾고자 하는 값이라면

pair.second(몇 장)를 return하는 방식으로 풀이하면 되겠습니다.

pair에 값을 저장해줄 때는 어차피 v가 정렬이 되있기 때문에 

pair에 가장 작은 값부터 순서대로 들어갈 것이고, 같으면 pair.second++,

pair가 가지고 있는 값이 v와 다르다는 것은 더 이상 같은 카드가 없다는 의미로 삽입(push_back)해줍니다.

이후 이분탐색을 이용하여 답 출력!

#include <stdio.h>
#include <vector>
#include <algorithm>

using namespace std;

vector <pair<int, int>> compare;

void recursive(int left, int right, int target)
{
	int ans = 0;
	while (left <= right)
	{
		int mid = (left + right) / 2;
		if (target == compare[mid].first)
		{
			ans = compare[mid].second;
			break;
		}

		else if (target < compare[mid].first)
		{
			right = mid - 1;
		}
		else if (target > compare[mid].first)
		{
			left = mid + 1;
		}
	}

	printf("%d ", ans);
}
int main(void)
{
	vector<int> v;
	vector<int>v1;
	int n;

	scanf("%d", &n);

	int num;
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &num);
		v.push_back(num);
	}


	sort(v.begin(), v.end());

	compare.push_back({v[0], 1});

	int idx = 0;
	for (int i = 1; i < n; i++)
	{
		if (v[i] == compare[idx].first)
			compare[idx].second++;

		else {
			compare.push_back({ v[i],1 });
			idx++;
		}
	}

	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &num);
		v1.push_back(num);
	}
	for (int i = 0; i < n; i++)
	{
		recursive(0, compare.size()-1, v1[i]);
	}
}

 

근데, C++ STL에서 upper_bound와 lower_bound함수를 이용하면 매우 문제가 간편해집니다..

 

upper_bound는 내가 찾고자 하는 값보다 '큰 값'이 처음으로 출현한 위치를 반환.

lower_bound는 내가 찾고자 하는 값보다 '크거나 같은' 값이 처음 출현한 위치를 반환.

 

-10 -10 2 3 3 6 7 10 10 10에서

 

-10이 처음 출현한 위치는 0, (lower_bound)

-10보다 큰 값이 처음 출현한 위치는 2(upper_bound)

즉, 이 두 개를 빼주어 2개라는 것을 안다는 것입니다.

 

3이 처음 출현한 위치는 3, 

3보다 큰 값이 처음 출현한 위치는 5.

이 두개를 빼면 2개.

 

이 upper_bound와 lower_bound 함수 자체가 이미 O(logN)의 시간복잡도로 구현이 되어있기 때문에,

그냥 사용하면 됩니다... 문제해결방법을 더 많이 알면 이런 것을 바로바로 떠올릴 수 있겠죠..? 

STL의 편리성을 적극 이용합시다..!!

 

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

int main(void)
{

	ios::sync_with_stdio(false);
	cin.tie(NULL);
	vector<int> a;
	vector<int> b;

	int n;
	cin >> n;
	a.resize(n);
	for (int i = 0; i < n; i++)
		cin >> a[i];

	cin >> n;
	b.resize(n);

	for (int i = 0; i < n; i++)
		cin >> b[i];

	sort(a.begin(), a.end());

	for (int i = 0; i < n; i++)
	{
		cout << upper_bound(a.begin(),a.end(), b[i]) - lower_bound(a.begin(),a.end(), b[i]) << " ";
	}
}