맞춰봐
문제
규현이는 멍청하다. 왜냐하면, 1~10까지 수 밖에 모르기 때문이다. 어느 날 규현이 옆을 지나가던 태석이가 규현이를 보고 이렇게 외쳤다. "빵빵!!" 규현이는 "아하!" 하면서 세상에는 빵이란 수도 있구나 했다. 그날 이후로 규현이는 매일 친구들을 볼 때면 "빵빵!!" 거리면서 인사를 했다. 규현이의 친구 중에는 태방이가 있다. 자꾸 규현이가 "빵빵!!" 거릴때 마다 자신을 놀리는 것 처럼 생각했던 태방이는 규현이에게 그건 "빵이 아니고 영이야" 라고 가르쳐 줬다.
이제 규현이는 0~10까지 수를 알고 있다. 어느 날 자신이 알고 있는 숫자를 까먹지 않으려고 종이에 1~10까지 수를 썻다. (0은 잠시 까먹었다) 규현이의 친구 석원이는 밀덕이다. 계급을 엄청나게 좋아해서, 규현이가 써 놓은 숫자에 이등병 마크인 -를 모두 그렸다. 석원이는 규현이에게 이렇게 말했다. "너, 우리 위대하신 미하엘 칼라시니코프께서 뭐라고 했는지 알아? 단순함과 신뢰성, 그리고 저렴한 가격이 최고야!"
규현이는 그 말을 듣고서 아하 세상에는 음수도 있구나 했다.
이제 규현이가 아는 수는 -10부터 10까지 20개가 되었다. 아차, 0을 빼먹었구나, 21개가 되었다.
근처 사파리에 놀러간 규현이는 사파리 가이드 승환이와 함께 관광을 시작했다. "저기, 사자 1마리가 보이죠? 그 옆이 그 사자 부인이에요. 그러니깐, 1 더하기 1은 2죠" 규현이는 덧셈을 익혔다. "저 사자는 아까 그 사자의 자식 2마리 입니다. 그럼 총 사자는 몇 마리이지요?" 이제 규현이는 1+1을 제외한 다른 덧셈도 할 수 있다. 만세!
인도네시아에 놀러간 규현이는 자바 섬에 방문했다. 자바 섬에는 자바 커피를 재배하는 홍태석 농부가 있었다. 홍태석은 "ㅋㅋㅋ 님 음수와 양수와 0의 차이도 모름?" 하면서 음수와 양수와 0을 설명해주었다.
지금까지 배운 것을 종합해서, 한국으로 돌아오는 비행기에서 규현이는 종이에 수를 N개 썼다. (규현이가 아는 가장 큰 수는 10이기 때문에, 수를 10개까지만 쓸 수 있다.) 그 다음에, 가능한 모든 N*(N+1)/2개의 구간의 합을 구했다. 이 것을 해인이는 행렬로 표현했다.
규현이가 쓴 수를 A라고 하면, A[i]는 규현이가 i번째 쓴 수이다. 그리고, S[i][j]는 A[i]부터 A[j]까지 합이 0보다 크면 +, 0이면 0, 0보다 작으면 -이다. 여기서 i는 항상 j보다 작거나 같다. 이렇게 배열을 채우면 배열에는 총 N*(N+1)/2개의 문자가 있다. (+, -, 0 중 하나) 이 S 배열이 주어졌을 때, 규현이가 쓴 N개의 수 A를 구해서 출력하면 된다. 규현이는 -10부터 10까지의 정수밖에 모르기 때문에, A도 -10부터 10까지의 정수로만 이루어져 있어야 한다.
입력
첫째 줄에 수열의 크기 N이 주어진다. N은 10보다 작거나 같은 자연수이다. 둘째 줄에는 N(N+1)/2 길이의 문자열이 주어진다. 처음 N개의 문자는 부호 배열의 첫 번째 줄에 해당하고, 다음 N-1개의 문자는 두 번째 줄에 해당한다. 마찬가지로 마지막 문자는 N번째 줄에 해당하는 문자다.
출력
첫째 줄에 수열의 원소 N개를 빈 칸을 사이에 두고 출력한다. 답이 여러 가지 일 경우에는 아무거나 출력하면 된다.
예제 입력 1
4 -+0++++--+
예제 출력 1
-2 5 -3 1
출처
- ICPC > Regionals > Asia Pacific > Korea > Asia Regional - Seoul 2008 G번
- 문제의 오타를 찾은 사람: 1207koo
- 문제를 번역한 사람: baekjoon
링크
- ACM-ICPC Live Archive
N개의 수열과 부호가 주는 힌트와의 관계는 다음과 같다.
수열 = [-2, 5, -3, 1]
에 대해 부호와의 관계를 살펴보면
이렇게 index 0부터 1, 2, ..., 끝까지의 합과 부호가 같다.
부호값이 있는 이차원 배열을 부호[][]
라고 한다면,
그럼 그 다음 줄의 부호들은 무엇? index 1부터 끝까지의 합에 대한 부호이다.
여기 또한,
그 다음 index 2부터 끝까지의 합에 대한 부호를 보자.
그리고 마지막 index부터 끝까지의 합에 대한 부호로 끝이 난다.
즉 부호(x,y) 좌표에 해당하는 부호 값은 수열의 x~y까지의 합에 대한 부호와 상응해야 한다.
그럼 부호값을 이러한 이차원 배열로 만들어서 검사해주면 좋을 것 같다!
function b1248(){
var fs = require('fs');
var input = fs.readFileSync('/dev/stdin').toString().split('\n');
// var input = fs.readFileSync(__dirname + '/stdin.txt').toString().replace(/\r/g, "").split('\n');
var n = parseInt(input[0]) // n값
var buho = input[1] // 부호 문자열
var map = create2DArray(n, n) // 부호 값을 넣을 2차원 배열 map
var idx = 0
for(var i=0; i<n; i++){
for(var j=i; j<n; j++){
map[i][j] = buho[idx++] // 문자열로 들어온 부호들을 차례로 map에 넣어주기
}
}
var arr = new Array(n) // 답을 넣을 배열
solve(0, arr, map, n)
}
function create2DArray(row, col){ // 2차원배열 만드는 함수
var array = new Array(row)
for(var i=0; i<row; i++){
array[i] = new Array(col)
}
return array
}
function solve(count, arr, map, n){
if(count === n){
var str = ''
for(var s=0; s<n; s++){
str += arr[i].toString()+' '
}
console.log(str)
process.exit(0)
}
for(var i=-10; i<=10; i++){
arr[count] = i
if(check(count, arr, map)){
solve(count+1, arr, map, n)
}
}
}
function check(index, arr, map){ // 유망성 검사
for(var i=0; i<=index; i++){
var sum = 0
for(var j=i; j<=index; j++){
sum += arr[j]
if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){
return false
}
}
}
return true
}
개인적으로 유망성 검사 부분을 코드화 하는데 고민을 많이 했다. 이 부분을 중점으로 정리할 예정 !
- 백트래킹을 돌릴 함수 solve를 만들어준다.
- solve 함수를 통해 정답을 넣을 배열 arr를 만들고, 여기에 -10~10까지의 수를 넣어본다.
- arr에 해당 수를 넣어도 되는지 유망성 검사 시작 !
3-1. 유망성을 알아보기 위해 필요한 건?
👉🏻 arr배열, map(부호 정보가 담긴 이차원배열), 현재 인덱스
그럼 -10부터 차근차근(하지만 살짝 띄엄띄엄..) 살펴보자
arr에 -10을 넣는다.
3-1. 현재 상태 :
count = 0 arr = [ -10 ] map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
3-2. -10을 넣어도 되는지 유망성 검사를 위해 check 함수에 들어가면,
check(0, [ -10 ], map){ for(var i=0; i<=0; i++){ var sum = 0 for(var j=0; j<=0; j++){ sum += -10 // sum = -10 // map[i][j] => map[0][0] = '-' // sum < 0 , map값도 '-' if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ // map[0][0] === sum < 0 ? '-' : '+' 아니까 거치지 않음 return false } } } return true }
true로 반환됨 => 그럼 count+1를 해주어 solve함수를 시작
다시 solve함수로 들어가arr[1]
에 -10~10까지의 숫자를 넣어본다.count = 1 arr = [ -10, -10 ] // arr[1] = -10 map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
check(1, [ -10, -10 ], map){ for(var i=0; i<=1; i++){ var sum = 0 for(var j=0; j<=1; j++){ sum = -10 + -10 // sum = -10 | sum = -20 // map[i][j] => map[0][0] = '-'| map[i][j] => map[0][1] = '+' // sum < 0 , map값도 '-' | sum < 0, map값은 '+' if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ // map[0][1]의 부호와 sum의 값이 맞지 않아 return false return false } } } return true }
false로 반환됨 => 그럼 재귀에서 빠져나와
arr=[ -10 ]
인 상태로 되돌아간다.
(하지만 -10+i가 양수가 되는 i는 -10~10 범위 내에 없다...)3-3. 고로 재귀에서 빠져나와 arr = [ ]인 상태로 되돌아간다.
-10의 다음 수 -9를 넣음
현재상태:count = 0 arr = [ -9 ] map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
check(0, [ -9 ], map){ for(var i=0; i<=0; i++){ var sum = 0 for(var j=0; j<=0; j++){ sum = -9 // sum = -9 // map[i][j] => map[0][0] = '-' // sum < 0 , map값도 '-' if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ // map[0][0]의 부호와 sum의 값이 맞으니 이곳을 거치지 않음 return false } } } return true }
true가 반환됨 => 그럼 count+1을 해주어 solve함수 시작
arr[1]
에 -10부터 10까지 넣어본다 => 현재 -10이 들어간다.count = 1 arr = [ -9, -10 ] map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
check(1, [ -9, -10 ], map){ for(var i=0; i<=1; i++){ var sum = 0 for(var j=i; j<=1; j++){ sum = -9 // sum = -9 | sum = -19 // map[i][j] => map[0][0] = '-' | map[i][j] => map[0][1] = '+' // sum < 0 , map값도 '-' | sum < 0 , map값은 '+' if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ // map[0][1]의 부호와 sum의 값이 맞지 않아 return false return false } } } return true }
false가 반환됨 => 재귀에서 빠져나와
arr=[ -9 ]
인 상태로 되돌아간다.
그렇게arr[1]
에 -10~10까지 숫자를 넣어보며 -9+i가 양수가 되는 수를 찾아보다가...i=10
의 경우에 왔다면count = 1 arr = [ -9, 10 ] map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
check(1, [ -9, 10 ], map){ for(var i=0; i<=1; i++){ var sum = 0 for(var j=i; j<=1; j++){ sum = -9 + 10 // sum = -9 | sum = 1 // map[i][j] => map[0][0] = '-' | map[i][j] => map[0][1] = '+' | map[1][1] = '+' // sum < 0 , map값도 '-' | sum > 0 , map값도 '+' | sum > 0, map = '+' if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ // map[i][j]의 부호와 sum값이 일치하므로 이곳을 거치지 않음 return false } } } return true }
true가 반환됨 => count+1를 해주어 solve 시작
(살짝 생략해서)arr[2]
에 들어갈 i값으로 (-9 + 10 + i가 0이 되는 수인)i=-1
의 경우에 왔다면count = 2 arr = [ -9, 10, -1] map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
check(2, [ -9, 10, -1 ], map){ for(var i=0; i<=2; i++){ var sum = 0 for(var j=i; j<=2; j++){ sum = -9 // sum = -9 | sum = 1 // map[i][j] => map[0][0] = '-' | map[0][1] = '+' | map[0][2] = '0' | map[1][1] = '+' | map[1][2] = '+' | map[2][2] = '-' // sum(-9) < 0, map = '-' | sum(1) > 0, map = '+' | sum(0) == 0, map = '0' | sum(10) > 0, map = '+' | sum(9) > 0, map = '+' | sum(-1) < 0, map = '-' if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ // map[i][j]의 부호와 sum값이 일치하므로 이곳을 거치지 않음 return false } } } return true }
true가 반환됨 => count+1을 해주어 solve 시작
arr[3]
으로 들어갈 i는? -9 + 10 + -1 + i를 해주어 양수가 되는 수
그렇다면 i가 될 수 있는 수는? 1~10
그런데 map[2][3]은 '-'이므로, -1 + i이 음수여야 한다. 하지만 1~10중 어느 것을 i로 넣어도 음수가 되지 못한다.
결국 모든 수가 false 되고... 재귀를 부른 부모 함수를 따라 돌아간다.
-9는 -9와 i를 더했을 때 양수가 되는 i값이 10밖에 없었는데 이 경우가 false가 되었으므로 결국 처음으로 되돌아가게 된다.3-4. 또 재귀에서 빠져나와 arr = [ ]의 상태로 되돌아감
-9 다음 수인 -8을 넣음
count = 0 arr = [ -8 ] map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
check(0, [ -8 ], map){ for(var i=0; i<=0; i++){ var sum = 0 for(var j=0; j<=0; j++){ sum = -8 // sum = -8 // map[i][j] => map[0][0] = '-' // sum < 0 , map값도 '-' if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ // map[0][0]의 부호와 sum의 값이 맞으니 이곳을 거치지 않음 return false } } } return true }
true가 반환됨 => count+1을 해주어 solve 시작
arr[1]
의 값이 될 i가 -10부터 시작해 (-8 + i를 해서 양수가 되는)i=9
의 경우에 도달하면count = 1 arr = [ -8, 9 ] map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
check(1, [ -8, 9 ], map){ for(var i=0; i<=1; i++){ var sum = 0 for(var j=i; j<=1; j++){ sum = -8 + 9 // sum = -8 | sum = 1 | sum = 9 // map[i][j] => map[0][0] = '-' | map[0][1] = '+' | map[1][1] = '+' // sum < 0 , map값도 '-' | sum(1) > 0, map = '+' | sum(9) > 0, map = '+' if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ // map[i][j]의 부호와 sum의 값이 맞으니 이곳을 거치지 않음 return false } } } return true }
true가 반환됨 => count+1을 해주어 solve 시작
arr[2]
의 값이 될 i가 -10부터 시작해 (-8 + 9 + i를 해서 0이 되는)i=-1
의 경우에 도달하면count = 2 arr = [ -8, 9, -1 ] map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
check(2, [ -8, 9, -1 ], map){ for(var i=0; i<=2; i++){ var sum = 0 for(var j=i; j<=2; j++){ sum = -8 + 9 + -1 // sum = -8 | sum = 1 | sum = 0 | sum = 9 | sum = 8 | sum = -1 // map[i][j] => map[0][0] = '-' | map[0][1] = '+' | map[0][2] = '0' | map[1][1] = '+' | map[1][2] = '+' | map[2][2] = '-' // sum < 0 , map값도 '-' | sum(1) > 0, map = '+' | sum(0) = 0, map = '0' | sum(9) > 0, map = '+' | sum(8) > 0, map = '+' | sum(-1) < 0, map = '-' if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ // map[i][j]의 부호와 sum의 값이 맞으니 이곳을 거치지 않음 return false } } } return true }
true가 반환됨 => count+1을 해주어 solve 시작
-8 + 9 + -1 + i를 해서 양수가 되는 i는 ? 1~10
하지만 아까와 마찬가지로 map[2][3]은 '-'이므로, -1+i는 음수여야 한다.
1~10까지의 모든 수는 -1과 더했을 때 음수가 되지 못하므로 false로 반환된다.
재귀를 따라 돌아가arr = [ -8 ]
의 상태까지 간다.3-4. -8 + i를 해서 양수가 되었던 9 다음 숫자인 10을 넣는다.
위와 같은 과정을 거쳐
arr = [ -8, 10, -2 ]
까지 채워진다.
arr[3]
에 들어갈 i값으로 -8 + 10 + -2 + i를 해서 양수가 나올 수 있는 i는? 1~10
map[2][3]이 '-'이므로, -2+i의 값이 음수일 수 있는 i는? 1
arr[3]
의 값으로 -10부터 시작하여 (중간과정 생략하고)i=1
인 경우에 도달했다면,count = 3 arr = [ -8, 10, -2, 1 ] map = [ [-, +, 0, +], [ , +, +, +], [ , , -, -], [ , , , +] ]
check(3, [ -8, 10, -2, 1], map){ for(var i=0; i<=3; i++){ var sum = 0 for(var j=i; j<=3; j++){ sum = -8 + 10 + -2 + 1 if(map[i][j] !== (sum == 0 ? '0' : (sum > 0 ? '+' : '-'))){ return false } } } return true }
(주석 달기가 불편해져서 그림으로 대체하였다)
sum을 계산하는 순서에 따라 각 행을 i, 각 열을 j라고 두었으니,
- i가 0일때 j=0, 1, 2, 3의 sum값과 map의 부호를 확인하고
- i가 1일때 sum을 0으로 초기화한 후 j=1, 2, 3의 sum값과 map 부호를 확인하고
- i가 2일때 sum을 0으로 초기화한 후 j=2, 3의 sum값과 map 부호를 확인하고
- i가 3일때 sum을 0으로 초기화한 후 j=3의 sum값과 map부호를 확인하였더니
=>모두 잘 맞게 떨어졌다.3-5. true가 return되어 count+1이 된다. 그렇게 시작된 solve의 count는 n값과 같아진다.
arr 배열에 map의 조건에 부합하는 숫자가 모두 담겼으니 이를 공백을 포함한 문자열로 붙여 출력한다.
※ "답이 여러 가지 일 경우에는 아무거나 출력하면 된다"고 하였으니 출력 후 곧바로 프로세스를 종료한다.
시행착오를 많이 겪은 문제다.
(멍청한 건 규현이가 아니라 저인 것 같습니다만...)
부호를 이차원 배열로 만들어 유망성 검사하는 방법을 떠올리지 못해 고생했다.
단순히 부호 = ['-', '+', '0', '+', '+', '+', '+', '-', '-', '+']
이런 식으로 일차원 배열로서 해결하려고 했는데, ... 확인된 부호들은 중간에 slice로 잘라내야 하나, shift()로 빼서 처리해야 하나 등등의 생각을 많이 했다.
또 점점 백트래킹으로 풀기보다는 다른 방식으로 풀려고 했던 것 같다.
일단 부호 배열의 앞 4자리 조건에 부합하는 n길이의 수열들을 모두 만들어 놓고, 그 중에서 다음 부호 조건들에 해당하는 애들을 솎아내는 방식;; 이러면 백트래킹이 무슨 소용?
몇시간 고민하면서 결국 이 문제는 정말 백트래킹으로 풀 수 있을거 같다는 생각이 들었고, 유망성 검사 부분을 코드화하는데 실패해 구글링의 도움을 받았다.
사기도 저하되고 동기부여도 떨어지는 요즘.. 어떻게 나를 다시 다독일지, 지치지 않게 할 수 있을지가 최대 고민거리다. 조금씩 점점 모든것이 하기 싫어지는 요즘 ㅜㅜ