알고리즘 문제 풀이 일지

백준 2024: 선분 덮기

여름하인 2024. 11. 19. 13:21

https://www.acmicpc.net/problem/2024

 

스위핑의 방법

이 문제는 꽤 고민을 하였다.

정렬을 한 뒤, 스위핑을 하면 될 것 같은데 스위핑을 어떻게 해야할지가 문제였다.

단순히 스위핑을 하면 [0, M]을 덮는 최소의 선분 갯수를 구할 수 없어서 특수한 방법이 필요할 것 같다.

 

일단 나는 이 문제를 해결하기 위해서, 예시 하나를 만들었다.

 

10

-1 5

3 7

4 10

0 0

답: 2

 

이 문제를 근본적으로 해결할 수 있는 예시라 생각했기 때문이다.

 

여러 시도

이렇게 생각을 하니, 우선 선분을 왼쪽 끝 순서대로 sort한 뒤에, 순서대로 오른쪽 끝을 우선순위 큐에 저장해서 현재 선분과 왼쪽 끝과 맞닿는 최소의 오른쪽 끝을 구하면 어떨까? 란 생각을 했었다. 위의 예시 위주로 생각해낸 방법이였고, 혹시나 하는 마음에 구현을 시도했었다. 하지만, 역시 이 아이디어는 아니였다.

 

그 다음, [0, M]을 덮는 선분 중 가잔 많은 곳을 덮는 선분을 우선적으로 고르는 것이 어떨까? 란 생각도 했었다.

하지만, 구현을 어떻게 해야할지가 문제였고, 무엇보다, 반례가 떠올랐다.

 

10

0 3

3 10

1 9

0 0

답: 2

오답: 3

 

그러면, 좌표를 기준으로 생각해서, 특정 좌표를 덮는 선분의 갯수가 가장 적은 선분을 먼저 고르면 어떨까? 란 생각을 했다. 하지만, 갯수가 중복되었을 경우 어떻게 처리해야할지가 문제였고, 역시 직감이였기에, 이 아이디어도 구현 도중 포기했다.

 

정답

여러 생각이 혼잡되었을 때, 나는 머리를 식히고, 처음부터 고민을 하기 시작했다. 우선 다시 왼쪽 끝을 기준으로 sort한 선분을 순서대로 맞이한다. 좌표가 0을 포함한 선분들을 생각했을 때 어떤 선분을 고르는 것이 이득일까?

[0, M]을 덮는 최대한의 선분이다.

 

여기까지 생각했을 때, 맨 위의 예시를 떠올렸다.

일단 0과 유일하게 겹치는 선분(-1, 5)은 무조건 포함을 해야한다.

이 다음 (3 7), (4 10) 중 하나를 골라야한다. 이 중 골라야하는 것은 (4, 10) 선분이다. 어째서? 좌표 5를 포함한 좌표 중

5 이상의 구간을 가장 많이 덮기 때문이다. 이미 5 이하의 구간은 덮어져있어서 무시 할 수 있다.

 

그러자 하나의 아이디어가 떠올랐다.

1. 우선 좌표 0을 포함한 선분 중 가장 큰 오른쪽 끝을 구한다. 이를 r1이라 하자.

2. 좌표 r1을 포함한 선분 중 가장 큰 오른쪽 끝을 구한다. 이를 r2라 하자.

3. 좌표 r2를 포함한 선분 중 가장 큰 오른쪽 끝을 구한다. 이를 r3라 하자.

....

선분은 최대한 덮는 것이 최소한의 선분을 구할 수 있다는 그리디적인 발상이다!

주의할 점은, 마지막 선분도 고를 수 있도록, 마지막에 sort 되는 더미 선분을 추가적으로 넣고

마지막에, 선분의 끝이 M을 넘는지 체크하자.

그렇게 나온 코드는 다음과 같다.

#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;

priority_queue<int> pq;
vector<pair<int, int>> line;
int L, R, M, ans = 0;

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL); cout.tie(NULL);
	cin >> M;
	while (true)
	{
		cin >> L >> R;
		if (L == 0 && R == 0) break;
		if (R < 0 || L > M) continue;
		line.push_back({L, R});
	}
	line.push_back({ M, 50001 });
	sort(line.begin(), line.end());
	int l = 0, r = 0;
	for (int i = 0; i < line.size(); i++)
	{
		if (line[i].first > l)
		{
			if (line[i].first > r)
			{
				ans = 0;
				break;
			}
			else
			{
				l = r;
				r = line[i].second;
				++ans;
			}
		}
		else
			r = max(r, line[i].second);
	}
	if (r < M) ans = 0;
	cout << ans << '\n';
}

스위핑과 그리디적인 발상이 섞인 재밌는 문제였다.