[ 백준/C++] 17472 다리만들기2 (삼성A형 기출문제,BFS,DFS 이용하기)

2023. 2. 12. 22:55🧡 Programming/💻 코딩 테스트

 

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

파란것은 섬이고 하얀것은 바다

그리고 회색의 다리로 섬들을 최소 길이로 이으라는 문제이다. 

 

문제 자체는 이해하기 어렵지 않다. 

다리는 직선이어야 하고 길이는 2이상이어야 한다. 

그리고 다리는 섬과 섬 사이를 이어야한다. 즉, 다리를 잇는 도중에 다리를 짓는 방향과 다른방향으로 섬이 있다고 해서 섬이 이어졌다고 판단하면 안된다. 

 

그런데 이 문제를 푸는 방법은 좀 생각을 잘 해야 한다. 

코드를 보면 좀 복잡 해 보이겠지만 함수의 이름을 최대한 직관적으로 쉽게 지정해 놓았으니 찬찬히 보면서 이해해 보면 좋을 것 같다. 

 

일단 풀이의 알고리즘 순서는 다음과 같다. 

 

1.dfs()를 이용하여 일단 섬의 개수를 구한다. 

  • dfs를 이용하여 동서남북 4방향 다 체크하면서 더 이상 갈 곳이 없으면 빠져나와서 섬 개수를 카운트 한다. 

2. findBridge()를 이용하여 각 섬들간의 최소 다리 길이를 구한다. 

  • 1번 섬과 2번 섬을 잇는 다리의 최소 길이는 a
  • 2번 섬과 3번 섬을 잇는 다리의 최소 길이는 b
  •  n번 섬과 n+1번 섬을 잇는 다리의 최소 길이는 x.. 이런식으로 구한다. 

3. 구한 최소 길이의 다리들을 모두 select_bridge라는 벡터에 넣어두고 조합을 계산한다. ( comb(C,inx,cnt) )

  • 예) select_bridge의 길이가 m 이라고 하였을 때,  개의 다리중에서 1,2,5,m-1 번 다리만 사용 하였을 경우, 모든 섬들을 다 잇는지 확인한다. ( CheckConnection() ) 

4. CheckConnection()에서 모든 섬들을 다 잇는 경우에 다리의 길이가 최소 길이인지 확인한다. (bridge_total_len)

 

#include <stdio.h>
#include <queue>
#include <vector>
#include <string.h>

int Y, X;
int map[15][15];
int mapnum[15][15];
bool visit[15][15];
int dx[] = { 0,0,1,-1 };
int dy[] = { 1,-1,0,0 };
int island_num = 0;
struct info { int sn, en, len; };
std::vector<info> bridge;
std::vector<bool> select_bridge;
int ans = 987654321;
int bridge_total_len = 0;

bool connection[10][10];
bool connection_visit[10];

void dfs(int y, int x, int n) { //cnt islands
	visit[y][x] = true;
	mapnum[y][x] = n;
	for (int i = 0; i < 4; i++) {
		int ny = y + dy[i];
		int nx = x + dx[i];
		if (ny < 0 || nx < 0 || ny >= Y || nx >= X) continue;
		if (map[ny][nx] == 0 || visit[ny][nx]) continue;
		dfs(ny, nx, n);
	}
}
int  findbridge(int sn, int en, int y, int x, int minlen_bridge) {
	int len = 0;
	for (int i = 0; i < 4; i++) {
		int ny = y + dy[i];
		int nx = x + dx[i];
		len = 0;
		while (true) {
			if (ny < 0 || nx < 0 || ny >= Y || nx >= X) break;

			if (mapnum[ny][nx] != en && map[ny][nx] == 1) break;///이게 중요! 
			if (map[ny][nx] == 0) {
				len++;
				ny += dy[i];
				nx += dx[i];
				continue;
			}
			else if (mapnum[ny][nx] == en && len >= 2) {
				//printf("len : %d \n", len);
				minlen_bridge = len < minlen_bridge ? len : minlen_bridge;
				break;
			}
			else// 
				break;
		}
	}
	return minlen_bridge;
}
void checkConnection() {
	memset(connection, 0, sizeof(connection));
	memset(connection_visit, 0, sizeof(connection_visit));

	for (int i = 0; i < bridge.size(); i++) {
		if (select_bridge[i]) {
			connection[bridge[i].sn][bridge[i].en] = true;
			connection[bridge[i].en][bridge[i].sn] = true;
		}
	}
	std::queue<int> Q;
	Q.push(1);
	connection_visit[1] = true;

	int island_cnt = 1;
	bool Flag = false;
	while (!Q.empty()) {
		int cur = Q.front();
		Q.pop();

		if (island_cnt == island_num) {
			Flag = true;
			break;
		}

		for (int i = 1; i <= island_num; i++) {
			if (cur == i) continue;
			if (connection[cur][i] && !connection_visit[i]) {
				connection_visit[i] = true;
				Q.push(i);
				island_cnt++;
			}
		}
	}

	if (Flag) {
		ans = ans > bridge_total_len ? bridge_total_len : ans;
	}
}
void comb(int C, int inx, int cnt) { // combination 하는법
	if (cnt == C) {
		bridge_total_len = 0;
		for (int i = 0; i < bridge.size(); i++) {
			if (select_bridge[i]) bridge_total_len += bridge[i].len;
		}

		checkConnection();
		return;
	}
	for (int i = inx; i < bridge.size(); i++) {
		if (select_bridge[i]) continue;
		select_bridge[i] = true;
		comb(C, i + 1, cnt + 1);
		select_bridge[i] = false;
	}
}
int main() {
	scanf("%d %d", &Y, &X);
	for (int y = 0; y < Y; y++) for (int x = 0; x < X; x++) scanf("%d", &map[y][x]);
	// calculate island num
	for (int y = 0; y < Y; y++) {
		for (int x = 0; x < X; x++) {
			if (map[y][x] == 0 || visit[y][x]) continue;
			island_num++;
			dfs(y, x, island_num);
		}
	}
	// find bridge 

	for (int sn = 1; sn <= island_num; sn++) {
		for (int en = sn + 1; en <= island_num; en++) {
			if (sn >= en) continue;
			int minlen_bridge = 999999;
			int minlen_bridge_cand = 999999;
			for (int y = 0; y < Y; y++) {
				for (int x = 0; x < X; x++) {
					if (mapnum[y][x] != sn) continue;
					minlen_bridge_cand = findbridge(sn, en, y, x, 99999999);
					//printf("%d \n", minlen_bridge_cand);
					minlen_bridge = minlen_bridge > minlen_bridge_cand ? minlen_bridge_cand : minlen_bridge;
				}
			}
			if (minlen_bridge != 999999) {
				bridge.push_back({ sn,en,minlen_bridge });
				select_bridge.push_back(false);
			}

		}
	}
    
	for (int C = 1; C <= bridge.size(); C++) {
		for (int i = 0; i < select_bridge.size(); i++) select_bridge[i] = false;
		comb(C, 0, 0);
	}
	if (ans == 987654321) printf("-1\n");
	else printf("%d\n", ans);
	return 0;
}

 

삼성 코테를 준비하면서 느낀건데 정신을 바짝 차려야 한다.

시뮬레이션이지만 적어도 이런 조합,순열,bfs,dfs를 열심히 돌려서 풀 수 있는 문제라면 희망의 끈을 놓지않고 실수하지 않는 연습을 해야 한다. 

 

나의 경우에는 함수와 변수명의 길이가 길더라도 풀어서 쓰는 것이 그 방법이었다.

왜냐면 코딩을 하다보면 내가 무엇을 하고있는 지 까먹기 때문.. 

이 함수의 input과 output을 정확히 기억하도록 하자. 

 

그리고 문제를 풀다보면 내가 실수하는 유형이 좀 정해져 있다. 

나의 경우에는 일단 오타였고 또 map의 크기, 조건등을 종종 꼼꼼하게 읽지 않아서 틀리는 경우였다.