BOJ 17472

다리만들기 2

문제출처

문제 해석

  • 모든 섬을 연결할 수 있는 최소 다리 길이를 찾는 문제입니다.
  • 연결할 다리는 일직선이고 길이 2 이상이어야 합니다.
  • 다리는 서로 통과할 수 있으며, 다리 길이는 각각 계산해서 더해야 합니다.

설계(20)

  • 할것이 아주 많은 문제입니다.
  • 첫번째로 독립적인 섬을 만들기 위해서 dfs를 통해 각 섬의 번호를 부여하고, 섬의 개수를 카운트합니다.
  • 두번째로 모든 섬에 대해서 다른 섬과의 최소 다리 길이를 계산합니다.
    1. 각 섬을 가진 좌표를 큐에 넣고 4방향을 탐색합니다.
    2. 이동할 좌표가 빈 공간이면 해당 방향으로 다른 섬이 나오는지 체크합니다.
    3. 해당 다리의 길이가 2 이상인 경우, 최소값을 갱신합니다.
  • 세번째로 다리를 선택한 후, 다리 길이 합의 최소값을 구합니다.
    1. 먼저 두번째 과정에서 구해진 가능한 다리들을 벡터에 저장합니다.
    2. (섬개수-1)보다 크거나 같은 개수의 다리를 선정합니다.
    3. 모든 섬이 연결되어있는지 체크하기 위해서, 선정된 다리를 지나갈 수 있게 체크한 후 bfs탐색을 진행합니다.
    4. 3번의 과정을 통해 모든 섬이 연결된 것이 확인됐으면 다리길이 합의 최소값을 갱신합니다.

구현(40)

코드
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;

#define MAXN 10
#define INF 987654321

int dist[7][7];
int num_bridge = 0;
int map[MAXN][MAXN];
bool visited[MAXN][MAXN];
vector<bool> seleted;
struct node {
	int u;
	int v;
	int weights;
};

vector<node> adj;
int ans = INF;
int N, M;
int dx[] = { 0,0,1,-1 };
int dy[] = { 1,-1,0,0 };
bool is_range(int x, int y) {
	return (x >= 0 && y >= 0 && x < N && y < M);
}
void get_num(int x, int y) {
	visited[x][y] = true;
	map[x][y] = num_bridge;
	for (int k = 0; k < 4; k++) {
		int nx = x + dx[k];
		int ny = y + dy[k];
		if (!is_range(nx, ny)) continue;
		if (visited[nx][ny] || !map[nx][ny]) continue;
		get_num(nx, ny);
	}
}
void create_bridge(int idx) {
	
	queue<pair<int, int> > q;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			if (map[i][j] == idx) {
				q.push({ i,j });
			}
		}
	}
	while (!q.empty()) {
		int x = q.front().first;
		int y = q.front().second;
		q.pop();
		for (int k = 0; k < 4; k++) {
			int nx = x + dx[k];
			int ny = y + dy[k];
			if (!is_range(nx, ny)) continue;
			if (map[nx][ny]) continue;
			int len = 0;
			while (1) {
				if (!is_range(nx, ny)) break;
				if (map[nx][ny]) {
					if (len >= 2 && dist[idx][map[nx][ny]] > len) {
						dist[idx][map[nx][ny]] = len;
						dist[map[nx][ny]][idx] = len;
					}
					break;
				}
				nx += dx[k];
				ny += dy[k];
				len++;
			}
		}
	}
}
bool check() {
	vector<vector<bool> > bridge(num_bridge+1, vector<bool>(num_bridge+1, false));
	vector<bool> used(num_bridge+1, false);
	queue<int> q;
	for (int i = 0; i < adj.size(); i++) {
		if (seleted[i]) {
			if (q.empty()) {
				q.push(adj[i].u);
				used[adj[i].u] = true;
			}
			bridge[adj[i].u][adj[i].v] = true;
			bridge[adj[i].v][adj[i].u] = true;
		}
	}
	int cnt = 1;
	while (!q.empty()) {
		int cur = q.front();
		q.pop();
		for (int i = 1; i <= num_bridge; i++) {
			if (dist[cur][i] == INF) continue;
			if (!used[i] && bridge[cur][i]) {
				used[i] = true;
				cnt++;
				q.push(i);
			}
		}
	}
	return cnt == num_bridge;
}

void connect_bridge(int cur, int w, int cnt) {
	if (cnt > num_bridge) return;
	if (cnt >= num_bridge - 1) {
		if (check()) {
			if (ans > w) ans = w;
			return;
		}
	}
	for (int i = cur; i < adj.size(); i++) {
		if (seleted[i]) continue;
		seleted[i] = true;
		connect_bridge(i, w + adj[i].weights,cnt+1);
		seleted[i] = false;
	}
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> N >> M;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			cin >> map[i][j];
		}
	}
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			if (map[i][j] && !visited[i][j]) {
				num_bridge++;
				get_num(i, j);
			}
		}
	}
	for (int i = 1; i <= num_bridge; i++) {
		for (int j = 1; j <= num_bridge; j++) {
			dist[i][j] = INF;
		}
	}
	for (int i = 1; i <= num_bridge; i++) {
		create_bridge(i);
	}
	
	for (int i = 1; i <= num_bridge; i++) {
		for (int j = i + 1; j <= num_bridge; j++) {
			if (dist[i][j] == INF)continue;
			adj.push_back({ i,j,dist[i][j] });
		}
	}
	seleted = vector<bool>(adj.size(),false);
	connect_bridge(0,0,0);
	if (ans == INF)cout << "-1\n";
	else cout << ans << "\n";
	return 0;
}
디버깅
  • 각 섬간 최소 다리길이를 찾을 때, while문에서 먼저 이동한 후 범위와 map을 체크해서 틀린 답이 나왔습니다.
  • 모든 섬이 연결되어있는지 확인하는 check함수에서 연결된 다리와 정점 방문여부를 체크하기 위한 변수의 범위를 잘못설정하여 수정하였습니다.
제출결과
  • 0ms

마무리

  • 여러가지 기법들이 종합된 알짜배기 문제입니다. 모든 섬이 연결되었는지 확인하는 부분에서 최소신장트리(MST)를 사용할 수 있었지만, 섬의 개수의 범위가 그리 크지 않아서 백트래킹을 사용할 수 있었습니다.
  • 그래프의 사용방법이 능숙해야 풀수 있는 문제인 것 같습니다. 구현 연습하기에 아주 좋은 문제입니다.