성장, 그 아름다운 향연

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

 

18290번: NM과 K (1)

크기가 N×M인 격자판의 각 칸에 정수가 하나씩 들어있다. 이 격자판에서 칸 K개를 선택할 것이고, 선택한 칸에 들어있는 수를 모두 더한 값의 최댓값을 구하려고 한다. 단, 선택한 두 칸이 인접

www.acmicpc.net

 

문제

크기가 N×M인 격자판의 각 칸에 정수가 하나씩 들어있다. 이 격자판에서 칸 K개를 선택할 것이고, 선택한 칸에 들어있는 수를 모두 더한 값의 최댓값을 구하려고 한다. 단, 선택한 두 칸이 인접하면 안된다. r행 c열에 있는 칸을 (r, c)라고 했을 때, (r-1, c), (r+1, c), (r, c-1), (r, c+1)에 있는 칸이 인접한 칸이다.

입력

첫째 줄에 N, M, K가 주어진다. 둘째 줄부터 N개의 줄에 격자판에 들어있는 수가 주어진다.

출력

선택한 칸에 들어있는 수를 모두 더한 값의 최댓값을 출력한다.

제한

- 1 ≤ N, M ≤ 10

- 1 ≤ K ≤ min(4, N×M)

- 격자판에 들어있는 수는 -10,000보다 크거나 같고, 10,000보다 작거나 같은 정수이다.

- 항상 K개의 칸을 선택할 수 있는 경우만 입력으로 주어진다.

 

 


 

출처

코드 플러스: [코딩 테스트 준비 - 기초] 강의

https://code.plus/course/51

 

 

접근 방법

 

백준에 있는 N과 M 시리즈를 응용한 문제라고 볼 수 있다.

 

문제의 키 포인트를 살펴보면

 

 

k개를 선택하되 인접한 칸과는 선택할 수 없다는 것이다.

 

 

 

하지만 나는 이 문장을 보고 "인접한 칸은 안되니 대각선 칸을 선택하면 되겠구나!" 라는 멍청미를 더했다.

(0,0) 에서 (1,1) 로 갈 수 있을 뿐만 아니라

(0,0) 에서 (0,2) 로도 칸을 지정할 수 있기 때문이다.

 

그렇다면 올바른 재귀 탐색은 어떻게 이룰 수 있을까?

 

 

step1.

 

fun go(idx: Int, sum: Int) {
...

for(i in 0 until n) {
        for(j in 0 until m) {
            if(c[i][j]) continue
            
            ...
            
}

 

재귀 함수의 일부분이다.

만약 재귀를 돌 때마다 i=0, j=0 부터 시작하면 어떻게 되는가?

 

(1,2) -> (2,1) -> (3,4) 의 결과를 구하게 된다면

(2,1) 에서 (1,2) -> (3,4) 로 중복된 결과를 구하게 된다.

 

 

 

step2.

 

fun go(idx: Int, sx: Int, sum: Int) {
...

for(i in sx until n) {
        for(j in 0 until m) {
            if(c[i][j]) continue
            
            ...
            
            go(idx+1, i, sum + g[i][j])
            
}

 

for문을 시작할 때 i의 값을 인자로 넘겨주어 오름차순으로 칸을 탐색할 수 있게 됐다.

즉, (1,2) -> (2,1) -> (3,4) 에서 (2,1) -> (1,2) -> (3,4) 로 되돌아가는 것을 방지한다.

 

그런데 만약 (1,2) -> (1,5) -> (3,4) 라면 어떻게 될까?

(1,5) -> (1,2) -> (3,4) 로 탐색할 수 있으므로 이 또한 중복을 야기한다.

 

위의 코드를 통해 행의 중복은 피했지만 열의 중복을 피하지 못했다.

 

 

 

step3.

 

fun go(idx: Int, sx:Int, sy: Int, sum: Int) {
	...
    
	for(i in sx until n) {
        for(j in (if(i==sx) sy else 0) until m) {
            if(c[i][j]) continue
            
            ...
            
            go(idx+1,i,j,sum + g[i][j])

 

(if(i==sx) sy else 0)

 

만약 이전 행과 현재 바라볼 행이 같다면 이전 열에 이어서 탐색하고,

만약 이전 행과 현재 바라볼 행이 같지 않다면 0부터 시작하겠다는 코드이다.

 

 

즉, (1,2) -> (1,5) -> (3,4) 에서 (1,5) 가 시작점이 될 때 (1,2) 와는 행이 같으므로 (1,2)를 제외하고 탐색하게 된다.

 

 

step2, step3 에서 중복을 피하는 과정을 '백 트래킹'이라고도 한다.

 

소스 코드

 

var ans = Int.MIN_VALUE
var n = 0
var m = 0
var k = 0
lateinit var g: Array<List<Int>>
lateinit var c: Array<BooleanArray>
val dx = intArrayOf(-1,1,0,0)
val dy = intArrayOf(0,0,-1,1)

fun main() = with(System.`in`.bufferedReader()) {
    val s = readLine().split(" ").map { it.toInt() }
    n = s[0]
    m = s[1]
    k = s[2]

    g = Array(n) {readLine().split(" ").map { it.toInt() }}
    c = Array(n) {BooleanArray(m)}

    go(0,0,0,0)
    println(ans)
}

private fun go(idx: Int, sx:Int, sy: Int, sum: Int) {
    if(idx==k) {
        if(ans < sum) ans = sum
        return
    }

    for(i in sx until n) {
        for(j in (if(i==sx) sy else 0) until m) {
            if(c[i][j]) continue

            var ok = true
            for(l in 0..3) {
                val nx = i + dx[l]
                val ny = j + dy[l]

                if(nx !in 0 until n || ny !in 0 until m) continue
                if(c[nx][ny]) ok = false
            }

            if(ok) {
                c[i][j] = true
                go(idx+1, i,j, sum + g[i][j])
                c[i][j] = false
            }
        }
    }
}

'algorithm > 브루트포스' 카테고리의 다른 글

백준 15652) N과 M (4)  (0) 2021.07.07
백준 15651) N과 M (3)  (0) 2021.06.25
백준 15650) N과 M (2)  (0) 2021.06.25
백준 15649) N과 M (1)  (0) 2021.06.25
profile

성장, 그 아름다운 향연

@dev_minoflower

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...