알고리즘 문제 풀이 일지

백준 13334: 철도

여름하인 2024. 10. 22. 15:25

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

 

기발한 아이디어?

이 문제를 보며 관찰한 결과, 철로 위치의 끝은 적어도 한 사람의 집과 사무실 끝을 둘 것이라 생각했다.

그렇다면, Set을 사용해서, 각 사람의 집과 사무실 위치를 저장해서 중복을 제거한 다음

(해당 위치 +- 철로 길이) 내에 얼마나 많은 출근 길이 겹치는 지 확인하면 정답일 것이다.

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

using namespace std;
vector<pair<int, int>> P;
int N, H, O, D, cnt = 0, ans = 0;
set<int> S;
int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL); cout.tie(NULL);
	cin >> N;
	for (int i = 0; i < N; i++)
	{
		cin >> H >> O;
		if (H > O) swap(H, O);
		P.push_back({ H, O });
	}
	sort(P.begin(), P.end());
	cin >> D;
	for (int i = 0; i < N; i++)
	{
		S.insert(P[i].first - D);
		S.insert(P[i].first);
		S.insert(P[i].second - D);
		S.insert(P[i].second);
	}
	auto iter = S.begin();
	for (int i = 0; i < N;)
	{
		if (P[i].second - P[i].first > D)
			++i;
		else if (P[i].first >= *iter && P[i].second <= (*iter) + D)
		{
			++cnt;
			++i;
		}
		else
		{
			ans = max(cnt, ans);
			cnt = 0;
			++iter;
		}
	}
	ans = max(cnt, ans);
	cout << ans << '\n';
}

하지만, 막상 구현을 시도 하려니, 막막해졌다. 막연히 스위핑 혹은 슬라이딩 윈도우를 사용하면, 어떻게든 될것이라 생각했는데, 결국엔 실패했다.

 

스위핑에서 다시 시작

결국 처음부터 다시 시작했다. 만약 이 문제가 스위핑 문제라면, 어떻게 해결할까? 일단 출근길을 시작 구간 순으로 정렬할 것이다. 스위핑을 하며, 현재의 선분과 다음 선분을 합치고(예로 1 3과 5 8을 합치면 현재 선분은 1 8이 된다.), 그 선분이 철로 길이 L보다 작으면 어떻게 할까?

 

처음엔 현재 선분을 버릴까 생각했는데, 현재 선분 중간에 정답이 될 가능성이 있다. 그러면 이전의 선분들의 시작 점들을 저장하고, 현재 선분이 L보다 작을때 까지 현재 선분의 시작점을 그 점들의 시작점으로 두면 정답이지 않을까? 이 아이디어를 바탕으로 구현을 시도했다.

#include <iostream>
#include <queue>
#include <algorithm>

using namespace std;
vector<pair<int, int>> P;
int N, H, O, D, cnt = 0, ans = 0, l, r = 0;
queue<int> q;

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL); cout.tie(NULL);
	cin >> N;
	for (int i = 0; i < N; i++)
	{
		cin >> H >> O;
		if (H > O) swap(H, O);
		P.push_back({ H, O });
	}
	sort(P.begin(), P.end());
	cin >> D;
	for (int i = 0; i < N; i++)
	{
		if (P[i].second - P[i].first > D) continue;
		while (!q.empty() && P[i].second - q.front() > D)
		{
			--cnt;
			q.pop();
		}
		q.push(P[i].first);
		++cnt;
		ans = max(ans, cnt);
	}
	ans = max(ans, cnt);
	cout << ans << '\n';
}

반례

혹시 내 코드가 틀리지 않을까 불안해진 나는 질문 게시판에 반례가 없는지 들여다보았다. 그러자, 반레를 하나 발견하였다.

 

4
1 4
1 4
2 5
3 4
3

 

이 예시의 정답은 3인데, 내 코드는 2로 표시하였다.

이것을 바탕으로 생각해본 결과, 정렬이 잘못된 것을 발견하였다.

기존의 스위핑 방식대로, 시작 구간 순으로 정렬하였는데, 이 문제의 경우 끝 구간 순으로 정렬해야한다. 그래야 선분을 합칠 때, 해당 구간 내의 겹치는 구간을 제대로 카운팅할 수 있다.

그리고, 시작 위치는 기존 코드와 달리 둘숙날숙 할 수 있으니 Queue를 Priority_Queue로 수정한다.

 

#include <iostream>
#include <queue>
#include <algorithm>

using namespace std;
vector<pair<int, int>> P;
int N, H, O, D, cnt = 0, ans = 0, l, r = 0;
priority_queue<int> q;

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL); cout.tie(NULL);
	cin >> N;
	for (int i = 0; i < N; i++)
	{
		cin >> H >> O;
		if (H > O) swap(H, O);
		P.push_back({ O, H });
	}
	sort(P.begin(), P.end());
	cin >> D;
	for (int i = 0; i < N; i++)
	{
		if (P[i].first - P[i].second > D) continue;
		while (!q.empty() && P[i].first + q.top() > D)
		{
			--cnt;
			q.pop();
		}
		q.push(-P[i].second);
		++cnt;
		ans = max(ans, cnt);
	}
	ans = max(ans, cnt);
	cout << ans << '\n';
}

간만에 재미있는 스위핑 문제였다.