mojo's Blog

[백준 9879] Cross Country Skiing 본문

백준/etc

[백준 9879] Cross Country Skiing

_mojo_ 2022. 3. 6. 19:00

문제 링크 : 9879번: Cross Country Skiing (acmicpc.net)

 

9879번: Cross Country Skiing

The cross-country skiing course at the winter Moolympics is described by an M x N grid of elevations (1 <= M,N <= 500), each elevation being in the range 0 .. 1,000,000,000. Some of the cells in this grid are designated as waypoints for the course. The org

www.acmicpc.net

 

"Disjoint set" 을 이용한 문제이다.

 

인풋으로 elevation 과 waypoint 가 주어질 때, 특정 waypoint 에서 모든 waypoint 으로 이동할 수 있도록 하는 D 의 최솟값을 구하는 문제이다.

문제에 주어진 인풋을 예로 들어서 어떻게 접근해야 할지 살펴보도록 한다.

 

※ 문제 접근 방법

 

① 인풋으로 주어진 elevation 과 waypoint 를 기반으로 하여 아래와 같이 그래프로 나타내도록 한다.

 

 

이때 waypoint 마다 색이 다른 것을 알 수 있는데 이는 서로 독립된 집합임을 나타내기 위함이다.

그렇다면, 위와 같은 정보를 담기 위해서 현재 정점, 인접한 정점, 거리를 담을 수 있는 벡터를 다음과 같이 정의한다.

vector<pair<int, pair<int, int>>> info : {거리, {현재 정점, 인접한 정점}} 

 

주어진 elevation 정보를 통해 거리(dist)를 현재 정점(cur)과 인접한 정점(next)을 갖고 다음과 같이 정의할 수 있다.

dist = | elevation(cur) - elevation(next) |   

 

② 위에서 거리, 현재 정점, 인접한 정점을 모두 info 에 담았다고 한다면, 거리가 오름차순이 되도록 정렬한다.

이제 Disjoint set 을 이용하기 위해 아래와 같은 2가지 배열을 이용하여 해결하도록 하였다.

 

  • int parent[x] : 정점 x가 속한 그룹은 parent[x] 이며, 정점 x가 waypoint 에 속한 그룹일 때 parent[x] = 0 으로 설정 (0 ≤ parent[x] ≤ M * N)
  • int check[x] : 정점 x가 속한 그룹은 check[x] 으로 설정 (1 ≤ check[x] ≤ M * N)

 

parent 배열은 특별하게도 어떤 정점 x가 속한 그룹이 waypoint 에 속한 그룹인 경우를 강조하기 위해 선언하였다. 

아래 코드와 같이 parent 배열과 check 배열을 초기화 할 수 있다.

void set_parent(int parent[])
{
	for (int i = 1; i <= R * C; i++)
	{
		if (point[i]) parent[i] = 0;
		else parent[i] = i;
	}
}

void set_check(int check[])
{
	for (int i = 1; i <= R * C; i++)
		check[i] = i;
}

 

그리고, parent 배열을 통해 waypoint 에 속한 그룹이 다음과 같이 묶이는 것을 알 수 있다.

 

크루스칼 알고리즘을 통해 MST 를 구하는 방법을 활용하여 D 값을 갱신할 수 있다.

우선, waypoint 끼리 cycle 이 되지 않도록 연결될 때를 카운팅하기 위한 변수 connect 를 선언하였다.

connect 값이 (waypoint 의 갯수 - 1) 이 될 때까지 D 값을 갱신해서 결과를 도출할 수 있다.

 

 connect == (waypoint 의 갯수 - 1) 일 때까지 D 값을 갱신하는 이유는?

 

위 경우는 서로 다른 waypoint 그룹이 하나의 waypoint 그룹으로 이뤄진 경우이다.

즉, 쉽게 말해서 서로 다른 waypoint 의 그룹이 (waypoint 의 갯수 - 1) 만큼 cycle 없이 연결된 경우로 트리임을 짐작할 수 있다.

 

그렇다면 정점 x, 정점 y, 그리고 두 정점 사이의 거리 dist 를 통해 두 정점 x, y 가 속한 그룹이 동일하지 않으면 두 정점의 그룹을 동일하게 하면서 D 값을 주어진 dist 를 통해 갱신할 수 있다.

이때 핵심은 두 정점이 속한 그룹이 waypoint 에 속한 그룹이라면, 두 waypoint 의 그룹이 동일하지 않을 때 두 그룹을 합치고 connect 값을 증가시키며 동일할 경우는 cycle 이 발생하므로 합치지 않으면 된다. 

 

 이에 대한 플로우는 다음과 같다.

 

거리가 1 인 경우에 대해서 그룹화 한 결과는 다음과 같다.

 

 

위와 같이 가장 작은 거리부터 시작하여 두 정점에 속한 그룹을 동일하게 하면서 D 값을 갱신하였다. 

계속해서 살펴보도록 한다.

 

거리가 2 인 경우에 대해서 그룹화 한 결과는 다음과 같다.

 

 

거리가 3 인 경우에 대해서 그룹화하려고 하지만 cycle 이 발생하기 때문에 이는 무시한다.

거리가 4, 5, ..., 20 인 경우에 대해서 그룹화 한 결과를 본다.

 

 

아직 거리 20 에 대해 완전히 그룹화하지 않았다.

위와 같이 빨간색인 그룹과 초록색인 그룹이 결국 waypoint 에 대한 그룹으로 해당 그룹을 그룹화한다면 다음과 같아진다.

 

 

그리고 거리 21에 대해 그룹화하게 된다면 다음과 같아진다.

 

 

connect 값이 이제 (waypoint 의 갯수 - 1) 가 되었으므로 종료한다.

그리고 D 값을 출력하면 된다.

 

이에 대한 코드는 아래와 같다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <math.h>

#define ll long long

using namespace std;

int R, C, P;
int dx[2] = { 1, 0 };
int dy[2] = { 0, 1 };
int elev[501][501];
int point[250001], parent[250001], check[250001];

void input()
{
	int cnt = 0;

	cin >> R >> C;
	for (int i = 1; i <= R; i++)
		for (int j = 1; j <= C; j++)
			cin >> elev[i][j];
	for (int i = 1; i <= R; i++)
	{
		for (int j = 1; j <= C; j++)
		{
			cin >> point[++cnt];
			if (point[cnt])
				P++;
		}
	}
}

int get_vertex(int x, int y)
{
	return ((x - 1) * C + y);
}

bool compare(pair<int, pair<int, int>> x, pair<int, pair<int, int>> y)
{
	return (x.first < y.first);
}

void set_info(vector<pair<int, pair<int, int>>>& info)
{
	for (int x = 1; x <= R; x++)
	{
		for (int y = 1; y <= C; y++)
		{
			for (int i = 0; i < 2; i++)
			{
				int next_x = x + dx[i], next_y = y + dy[i];
				if (next_x >= 1 && next_x <= R && next_y >= 1 && next_y <= C)
				{
					int dist = abs(elev[next_x][next_y] - elev[x][y]);
					info.push_back({ dist, {get_vertex(x, y), get_vertex(next_x, next_y)} });
				}
			}
		}
	}
	sort(info.begin(), info.end(), compare);
}

void set_parent(int parent[])
{
	for (int i = 1; i <= R * C; i++)
	{
		if (point[i]) parent[i] = 0;
		else parent[i] = i;
	}
}

void set_check(int check[])
{
	for (int i = 1; i <= R * C; i++)
		check[i] = i;
}

int get_parent(int x, int parent[])
{
	if (x == parent[x])
		return x;
	return (parent[x] = get_parent(parent[x], parent));
}

void go_union(int x, int y, int parent[])
{
	x = get_parent(x, parent);
	y = get_parent(y, parent);
	if (x > y) 
		parent[x] = y;
	else
		parent[y] = x;
}

void solution()
{
	vector<pair<int, pair<int, int>>> info;
	int D = 0, connect = 0;
	int from, to, dist, from_parent, to_parent;
	int from_check, to_check;

	set_info(info);
	set_parent(parent);
	set_check(check);
	for (int i = 0; i < info.size(); i++)
	{
		from = info[i].second.first, to = info[i].second.second, dist = info[i].first;
		from_parent = get_parent(from, parent), to_parent = get_parent(to, parent);
		from_check = get_parent(from, check), to_check = get_parent(to, check);
		if (from_parent != to_parent)
		{
			go_union(from, to, parent);
			go_union(from, to, check);
		}
		else if(from_parent == 0 && to_parent == 0)
		{
			if (from_check != to_check)
			{
				go_union(from, to, check);
				connect++;
			}
		}
		D = max(D, dist);
		if (connect == P - 1)
			break;
	}
	cout << D;
}

int main()
{
	input();
	solution();

	return 0;
}

 

'백준 > etc' 카테고리의 다른 글

[백준 25308] 방사형 그래프  (0) 2022.06.26
[백준 17136] 색종이 붙이기  (0) 2022.02.04
[백준 16991] 외판원 순회 3  (0) 2022.01.26
[백준 10993] 별 찍기 - 18  (0) 2022.01.09
[백준 2211] 네트워크 복구  (0) 2021.12.30
Comments