Algorithm/BOJ

백준 1981 배열에서 이동

삼쓰 웅쓰 2021. 4. 1. 21:05
728x90

출처: www.acmicpc.net/problem/1981

분류: 이분탐색


접근방식

이 문제는 n이 최대 100으로 100 x 100 배열이기 때문에 단순 무식하게 BFS 접근하면 시간초과를 맞게 됩니다.

어떻게 하면 방문을 범위를 제한할 수 있을까요? 이 문제에서도 이분탐색의 아이디어를 적용해볼 수 있습니다.

우선 차이가 가장 작아지는 경우는 언제일까요? 경로의 최대최소가 같은 경우면 차이가 최소가 될 수 있겠죠? 때문에 최소는 언제나 가능성이 있으니 0으로 둡니다.

그럼 차이가 최대가 될 때는 언제일까요? 배열에 나오는 최대값과 최소값이 모두 들어있을 때, 즉 최대 - 최소가 차이의 최대가 되겠네요.

저희는 최소를 구해야 하기 때문에 만약 0 ~ 8 까지 가능성이 있을 때, 차이를 4로해서 가는 경우를 찾았다면, 그보다 큰 경우는 생각할 필요가 없습니다. 따라서 이분탐색으로 범위를 줄여나갈 수 있습니다.

var low = 0
var high = maxValue-minValue

while(low <= high) {
  let mid = (low + high)/2
  if search(mid) {
    minDiff = min(minDiff, mid)
    high = mid-1
  } else {
    low = mid+1
  }
}

 

이제 저희는 특정한 차이값으로 탐색이 가능한지를 살펴보면 되겠다는 걸 알았습니다. 이건 어떻게 확인할까요?

아이디어는 4차이가 나는 경우를 확인하고 싶을 때, 최소값을 특정했다면 최대값의 범위를 제한할 수 있다는 겁니다.

최소값이 0이라고 할 때 차이 4를 확인하고 싶다면, 최대 4까지 가능하겠죠? 

이런식으로 [최소값 ~ 최소값 + 차이] 로 범위를 특정해서 해당 범위의 칸만 밟으면서 목적지까지 도달하는지 확인해나갈 수 있습니다.

for 문으로 최소값을 하나씩 올려가면서 그 사이의 칸들로만 도달이 가능한지 확인해볼 수 있습니다.

그리고 저희는 배열에서 나올 수 있는 최소와 최대값을 알고 있기 때문에 for문은 최소값 ~ 최대값-차이 사이만 확인하면 되겠다는 것도 알 수 있겠네요.

for min in minValue...maxValue-minDiff {
    
    guard min...min+minDiff ~= map[0][0] else { continue }
    
    var visit = [[Bool]](repeating: [Bool](repeating: false, count: n), count: n)
    visit[0][0] = true
    
    var q = [Point(0, 0)]
    while !q.isEmpty {
      let curr = q.removeFirst()
      
      for d in 0..<4 where 0..<n ~= curr.r + dr[d] && 0..<n ~= curr.c + dc[d] {
        let next = Point(curr.r + dr[d], curr.c + dc[d])
        guard !visit[next.r][next.c], min...min+minDiff ~= map[next.r][next.c] else { continue }
        if next.r == n-1, next.c == n-1 {
          return true
        }
        visit[next.r][next.c] = true
        q.append(next)
      }
    }
  }

 

BFS에서 최대 최소를 특정하는 방법이 핵심이었고, 이분탐색은 꼭 이분탐색이 아니라 투포인터로도 접근해볼 수 있을 것 같습니다. :)

 

해결방법

typealias Point = (r: Int, c: Int)

let n = Int(readLine()!)!
var map = [[Int]]()

var maxValue = 0
var minValue = 200
for _ in 0..<n {
  map.append(readLine()!.split(separator: " ").map { Int(String($0))! })
  maxValue = max(maxValue, map.last!.max()!)
  minValue = min(minValue, map.last!.min()!)
}

var minDiff = 201
var dr = [-1, 1, 0, 0]
var dc = [0, 0, 1, -1]

func search(_ minDiff: Int) -> Bool {
  
  for min in minValue...maxValue-minDiff {
    
    guard min...min+minDiff ~= map[0][0] else { continue }
    
    var visit = [[Bool]](repeating: [Bool](repeating: false, count: n), count: n)
    visit[0][0] = true
    
    var q = [Point(0, 0)]
    while !q.isEmpty {
      let curr = q.removeFirst()
      
      for d in 0..<4 where 0..<n ~= curr.r + dr[d] && 0..<n ~= curr.c + dc[d] {
        let next = Point(curr.r + dr[d], curr.c + dc[d])
        guard !visit[next.r][next.c], min...min+minDiff ~= map[next.r][next.c] else { continue }
        if next.r == n-1, next.c == n-1 {
          return true
        }
        visit[next.r][next.c] = true
        q.append(next)
      }
    }
  }
  
  return false
}

var low = 0
var high = maxValue-minValue

while(low <= high) {
  let mid = (low + high)/2
  if search(mid) {
    minDiff = min(minDiff, mid)
    high = mid-1
  } else {
    low = mid+1
  }
}

print(minDiff)

 

배운점

후 쉽지 않았다.... 다른 분들의 풀이를 참고했는데 처음에 제대로 이해를 못해서 범위를 산정하는 부분에서 꼬여서 몇 번을 틀렸다 ㅠㅠ

그래도 두 번째 골1 문제다.. 이제부터 좀 높은 레벨의 문제를 많이 풀어봐야겠다.