백준 17472. 다리 만들기 2(python)
2023. 4. 23. 22:34ㆍ알고리즘/백준
17472번: 다리 만들기 2
첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. 둘째 줄부터 N개의 줄에 지도의 정보가 주어진다. 각 줄은 M개의 수로 이루어져 있으며, 수는 0 또는 1이다. 0은 바다, 1은 땅을 의미한다.
www.acmicpc.net
입력
- 첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다.
- 둘째 줄부터 N개의 줄에 지도의 정보가 주어진다.
- 각 줄은 M개의 수로 이루어져 있으며, 수는 0 또는 1이다.
- 0은 바다, 1은 땅을 의미한다.
문제풀이
- 다리는 중간에 방향이 바뀌지 않고, 다른 다리와 겹칠 수 있다.
- 겹치는 경우에는 해당 칸을 각 다리에 모두 포함시킨다.
- 다리는 섬과 수직하게 만나야한다.
- 각 섬에 번호를 부여하고 연결할 수 있는 다리의 길이를 모두 구한 후
- 유니온 파인드를 이용해 (섬의 수 - 1)개의 다리로 연결될 수 있는 지 체크하는 방식으로 풀이했다.
N, M = [*map(int, input().split())]
visited = [[0]*M for _ in range(N)]
mapp = [0]*N
island_num = 1 # 각 섬에 번호 붙여줄 변수
bridge = [] # 다리 정보 입력할 리스트
cnt = 0
leng_sum = 0
for i in range(N):
mapp[i] = [*map(int, input().split())]
- 먼저 입력을 받는다.
for i in range(N):
for j in range(M):
if mapp[i][j] == 1 and visited[i][j] == 0: # 방문하지 않은 섬이면
bfs(i, j, island_num) # 탐색하며 번호 붙임
island_num += 1 # 다음 섬 위해 번호 1 증가
- bfs 탐색을 하여 각 섬에 순차적으로 번호를 부여한다.
- bfs 함수는 다음과 같다.
def bfs(y, x, num): # 각 섬에 번호 붙여주기 위한 bfs (num - 섬에 붙여줄 번호)
queue = deque()
queue.append((y, x))
visited[y][x] = 1
mapp[y][x] = num
while queue:
now = queue.popleft()
for i in range(4):
ny = now[0] + dy[i]
nx = now[1] + dx[i]
if 0 <= ny < N and 0 <= nx < M:
if visited[ny][nx] == 0 and mapp[ny][nx] == 1:
visited[ny][nx] = 1
mapp[ny][nx] = num
queue.append((ny, nx))
- dy, dx는 방향배열이다.
arr = [-1]*island_num
for i in range(N):
for j in range(M):
if mapp[i][j] != 0: # 섬이면
connect(i, j) # 어떤 섬과 다리 놓을 수 있는지 탐색
- 유니온 파인드를 구현하기 위해 어떤 섬과 연결되었는지 체크할 리스트(위 코드의 arr)을 하나 만든다
- 리스트의 길이는 위에서 구한 섬의 개수이다.
- 또한 각 섬에서 최소 길이의 다리로 어떤 섬과 연결될 수 있는지 체크한다. -> 위의 connect 함수
- connect 함수의 구조는 다음과 같다.
def connect(y, x): # 다리 연결 정보 생성할 함수
for i in range(4):
leng = 1
ny = y + dy[i]
nx = x + dx[i]
if 0 <= ny < N and 0 <= nx < M:
if mapp[ny][nx] == 0: # 만약 주변이 바다(0)라면
while True:
ny += dy[i] # 같은 방향으로 거리 늘리며
nx += dx[i]
if 0 <= ny < N and 0 <= nx < M:
if mapp[ny][nx] > mapp[y][x]: # 탐색 시작한 섬보다 번호가 큰 섬 만나면
if leng > 1: # 다리 길이 2 이상일 때
bridge.append([mapp[y][x], mapp[ny][nx], leng]) # 다리 연결
break
break
elif mapp[ny][nx] == mapp[y][x]: # 동일한 섬 만나면 다음 방향 탐색
break
elif mapp[ny][nx] == 0: # 바다 만나면
leng += 1 # 다리 길이 +1
else:
break
else: # 범위 벗어나면 다음 방향 탐색
break
- 섬의 한 점이면 connect 함수로 좌표를 넘겨준다. 후에 주변 4칸(상, 하, 좌, 우)를 탐색하여
- 바다가 있다면 거리를 늘리며 다른 섬을 만날 때, 탐색을 시작한 섬을 만날 때 혹은 지도의 범위를 벗어날 때까지 탐색한다.
- 조건에 맞으면 연결된 섬의 번호와 다리 길이를 기록한다.
if len(bridge) <= island_num - 3: # 다리 수가 부족하면 연결 불가
print(-1)
else:
bridge = sorted(bridge, key=lambda x:x[2]) # 다리 길이 오름차순으로 정렬
for i in bridge:
if findboss(i[0]) != findboss(i[1]): # 두 섬이 이어져 있지 않으면
leng_sum += i[2] # 다리 길이 합산
union(i[0], i[1]) # 연결 표시
cnt += 1 # 다리 개수 증가
if cnt == island_num-2: # 다리 개수가 (섬 수)-1이면 연결 완료
break
if cnt == island_num-2: # 연결 완료 시
print(leng_sum) # 다리 길이 합 출력
else: # 그렇지 않으면 -1 출력
print(-1)
- 위 함수의 결과에서 다리의 수가 최소로 필요한 수를 넘기지 못하면 -1 출력
- 그렇지 않으면 연결될 수 있는지 없는지 검사한다.
- 다리 길이를 기준으로 연결 가능한 정보를 정렬시킨다.
- findboss 함수를 사용하여 두 섬이 연결되어 있으면, 다음 다리로 넘어가고
- 그렇지 않다면, 연결한 후 다리길이를 결과값에 더해준다.
- findboss 함수와 union 함수의 구조는 아래와 같다.
def union(a, b): # 다리 놓은 섬 묶어줄 함수(동일한 보스를 갖도록)
fa, fb = findboss(a), findboss(b)
if fa == fb:
return
arr[fb] = fa
def findboss(a): # 섬의 보스 찾아줄 함수
if arr[a] == -1:
return a
ret = findboss(arr[a])
arr[a] = ret
return ret

'알고리즘 > 백준' 카테고리의 다른 글
| 백준 17471. 게리맨더링(python) (0) | 2023.05.08 |
|---|---|
| 백준 2116. 주사위 쌓기(python) (1) | 2023.05.01 |
| 백준 17281. ⚾ (0) | 2023.04.16 |
| 백준 4991. 로봇 청소기(python) (0) | 2023.04.16 |